From c590f80643105e37682d2fb5900b137aea8d74e3 Mon Sep 17 00:00:00 2001 From: Henry Avetisyan Date: Sat, 31 Dec 2016 01:10:04 -0800 Subject: [PATCH] Initial commit --- .gitignore | 21 + LICENSE | 202 + README.md | 65 + assembly/components/zms.xml | 87 + assembly/components/zpu.xml | 90 + assembly/components/zts.xml | 87 + assembly/pom.xml | 99 + clients/go/zms/Makefile | 20 + clients/go/zms/README.md | 54 + clients/go/zms/auth.go | 112 + clients/go/zms/client.go | 2377 +++ clients/go/zms/model.go | 3827 +++++ clients/go/zms/zms_schema.go | 1233 ++ clients/go/zts/Makefile | 20 + clients/go/zts/README.md | 36 + clients/go/zts/client.go | 755 + clients/go/zts/model.go | 1966 +++ clients/go/zts/zts_schema.go | 418 + clients/java/sia/README.md | 27 + clients/java/sia/pom.xml | 62 + .../main/java/com/yahoo/athenz/sia/SIA.java | 47 + .../com/yahoo/athenz/sia/impl/SIAClient.java | 526 + .../yahoo/athenz/sia/impl/SIAClientTest.java | 528 + .../java/sia/src/test/resources/dummy.ntoken | 0 .../java/sia/src/test/resources/logback.xml | 17 + .../sia/src/test/resources/svc.ntoken_test | 1 + .../java/sia/src/test/resources/testcfg.conf | 6 + .../sia/src/test/resources/testcfg_empty.conf | 6 + clients/java/zms/README.md | 23 + clients/java/zms/pom.xml | 227 + clients/java/zms/scripts/make_stubs.sh | 24 + .../com/yahoo/athenz/zms/ResourceError.java | 24 + .../yahoo/athenz/zms/ResourceException.java | 79 + .../com/yahoo/athenz/zms/ZMSAuthorizer.java | 139 + .../java/com/yahoo/athenz/zms/ZMSClient.java | 1691 ++ .../yahoo/athenz/zms/ZMSClientException.java | 25 + .../athenz/zms/ZMSRDLGeneratedClient.java | 1478 ++ ...zms.com.fasterxml.jackson.core.JsonFactory | 1 + ...zms.com.fasterxml.jackson.core.ObjectCodec | 1 + ....zms.com.fasterxml.jackson.databind.Module | 1 + ...shade.zms.javax.ws.rs.client.ClientBuilder | 1 + ...hade.zms.javax.ws.rs.ext.MessageBodyReader | 1 + ...hade.zms.javax.ws.rs.ext.MessageBodyWriter | 1 + ....shade.zms.javax.ws.rs.ext.RuntimeDelegate | 1 + ...fish.hk2.extension.ServiceLocatorGenerator | 1 + ...sfish.jersey.internal.spi.AutoDiscoverable | 2 + .../athenz/zms/ResourceExceptionTest.java | 88 + .../yahoo/athenz/zms/ZMSAuthorizerTest.java | 349 + .../yahoo/athenz/zms/ZMSClientMockTest.java | 1100 ++ .../com/yahoo/athenz/zms/ZMSClientTest.java | 2076 +++ .../com/yahoo/athenz/zms/ZMSLoadData.java | 202 + .../java/zms/src/test/resources/athenz.conf | 16 + .../java/zms/src/test/resources/logback.xml | 17 + clients/java/zpe/README.md | 71 + clients/java/zpe/pom.xml | 205 + .../com/yahoo/athenz/zpe/AuthZpeClient.java | 797 + .../java/com/yahoo/athenz/zpe/ZpeClient.java | 49 + .../java/com/yahoo/athenz/zpe/ZpeConsts.java | 68 + .../java/com/yahoo/athenz/zpe/ZpeMetric.java | 128 + .../yahoo/athenz/zpe/ZpeThreadFactory.java | 45 + .../com/yahoo/athenz/zpe/ZpeUpdMonitor.java | 112 + .../com/yahoo/athenz/zpe/ZpeUpdPolLoader.java | 486 + .../java/com/yahoo/athenz/zpe/ZpeUpdater.java | 99 + .../java/com/yahoo/athenz/zpe/ZpeYcrKey.java | 35 + .../com/yahoo/athenz/zpe/match/ZpeMatch.java | 24 + .../athenz/zpe/match/impl/ZpeMatchAll.java | 28 + .../athenz/zpe/match/impl/ZpeMatchEqual.java | 30 + .../athenz/zpe/match/impl/ZpeMatchRegex.java | 33 + .../zpe/match/impl/ZpeMatchStartsWith.java | 30 + .../yahoo/athenz/zpe/pkey/PublicKeyStore.java | 35 + .../zpe/pkey/PublicKeyStoreFactory.java | 25 + .../zpe/pkey/file/FilePublicKeyStore.java | 107 + .../pkey/file/FilePublicKeyStoreFactory.java | 29 + .../yahoo/athenz/zpe/MockMetricFactory.java | 94 + .../com/yahoo/athenz/zpe/TestAuthZpe.java | 1016 ++ .../com/yahoo/athenz/zpe/TestZpeMatch.java | 101 + .../com/yahoo/athenz/zpe/TestZpeMetric.java | 133 + .../yahoo/athenz/zpe/TestZpeUpdPolLoader.java | 165 + .../com/yahoo/athenz/zpe/TestZpeYcrKey.java | 35 + .../java/zpe/src/test/resources/angler.pol | 162 + .../java/zpe/src/test/resources/athenz.conf | 24 + .../zpe/src/test/resources/athenz_no_0.conf | 24 + clients/java/zpe/src/test/resources/empty.pol | 15 + .../java/zpe/src/test/resources/logback.xml | 16 + .../zpe/src/test/resources/logback_perf.xml | 16 + .../zpe/src/test/resources/pol_dir/.keepme | 0 .../java/zpe/src/test/resources/sports.pol | 32 + .../java/zpe/src/test/resources/sys.auth.pol | 32 + .../zpe/src/test/resources/zms_private_k0.pem | 9 + .../zpe/src/test/resources/zms_public_k0.pem | 4 + .../zpe/src/test/resources/zts_private_k0.pem | 15 + .../zpe/src/test/resources/zts_private_k1.pem | 15 + .../src/test/resources/zts_private_k17.pem | 15 + .../src/test/resources/zts_private_k99.pem | 15 + .../zpe/src/test/resources/zts_public_k0.pem | 6 + .../zpe/src/test/resources/zts_public_k1.pem | 6 + .../zpe/src/test/resources/zts_public_k17.pem | 6 + .../zpe/src/test/resources/zts_public_k99.pem | 6 + clients/java/zts/README.md | 27 + clients/java/zts/pom.xml | 235 + clients/java/zts/scripts/make_stubs.sh | 21 + .../com/yahoo/athenz/zts/ResourceError.java | 24 + .../yahoo/athenz/zts/ResourceException.java | 79 + .../java/com/yahoo/athenz/zts/ZTSClient.java | 1533 ++ .../yahoo/athenz/zts/ZTSClientException.java | 26 + .../yahoo/athenz/zts/ZTSClientService.java | 83 + .../athenz/zts/ZTSClientTokenCacher.java | 61 + .../athenz/zts/ZTSRDLGeneratedClient.java | 333 + ...zts.com.fasterxml.jackson.core.JsonFactory | 1 + ...zts.com.fasterxml.jackson.core.ObjectCodec | 1 + ....zts.com.fasterxml.jackson.databind.Module | 1 + ...shade.zts.javax.ws.rs.client.ClientBuilder | 1 + ...hade.zts.javax.ws.rs.ext.MessageBodyReader | 1 + ...hade.zts.javax.ws.rs.ext.MessageBodyWriter | 1 + ....shade.zts.javax.ws.rs.ext.RuntimeDelegate | 1 + ...fish.hk2.extension.ServiceLocatorGenerator | 1 + ...sfish.jersey.internal.spi.AutoDiscoverable | 2 + .../com/yahoo/athenz/zts/ZTSClientMock.java | 326 + .../athenz/zts/ZTSClientServiceProvider.java | 58 + .../com/yahoo/athenz/zts/ZTSClientTest.java | 2153 +++ .../athenz/zts/ZTSRDLGeneratedClientMock.java | 45 + .../com.yahoo.athenz.zts.ZTSClientService | 1 + .../java/zts/src/test/resources/athenz.conf | 17 + core/zms/README.md | 12 + core/zms/pom.xml | 67 + core/zms/scripts/make_stubs.sh | 27 + .../yahoo/athenz/provider/ProviderSchema.java | 187 + .../com/yahoo/athenz/provider/Tenant.java | 85 + .../athenz/provider/TenantResourceGroup.java | 72 + .../yahoo/athenz/provider/TenantState.java | 24 + .../java/com/yahoo/athenz/zms/Access.java | 37 + .../java/com/yahoo/athenz/zms/Assertion.java | 84 + .../com/yahoo/athenz/zms/AssertionEffect.java | 23 + .../com/yahoo/athenz/zms/DanglingPolicy.java | 49 + .../com/yahoo/athenz/zms/DefaultAdmins.java | 38 + .../java/com/yahoo/athenz/zms/Domain.java | 151 + .../java/com/yahoo/athenz/zms/DomainData.java | 117 + .../com/yahoo/athenz/zms/DomainDataCheck.java | 110 + .../java/com/yahoo/athenz/zms/DomainList.java | 50 + .../java/com/yahoo/athenz/zms/DomainMeta.java | 112 + .../com/yahoo/athenz/zms/DomainModified.java | 50 + .../yahoo/athenz/zms/DomainModifiedList.java | 38 + .../com/yahoo/athenz/zms/DomainPolicies.java | 52 + .../com/yahoo/athenz/zms/DomainTemplate.java | 38 + .../yahoo/athenz/zms/DomainTemplateList.java | 38 + .../java/com/yahoo/athenz/zms/Entity.java | 50 + .../java/com/yahoo/athenz/zms/EntityList.java | 39 + .../com/yahoo/athenz/zms/HostServices.java | 50 + .../java/com/yahoo/athenz/zms/Membership.java | 71 + .../java/com/yahoo/athenz/zms/Policies.java | 38 + .../java/com/yahoo/athenz/zms/Policy.java | 61 + .../java/com/yahoo/athenz/zms/PolicyList.java | 51 + .../zms/ProviderResourceGroupRoles.java | 83 + .../com/yahoo/athenz/zms/PublicKeyEntry.java | 49 + .../com/yahoo/athenz/zms/ResourceAccess.java | 49 + .../yahoo/athenz/zms/ResourceAccessList.java | 38 + .../main/java/com/yahoo/athenz/zms/Role.java | 86 + .../com/yahoo/athenz/zms/RoleAuditLog.java | 82 + .../java/com/yahoo/athenz/zms/RoleList.java | 51 + .../main/java/com/yahoo/athenz/zms/Roles.java | 38 + .../yahoo/athenz/zms/ServerTemplateList.java | 38 + .../yahoo/athenz/zms/ServiceIdentities.java | 38 + .../com/yahoo/athenz/zms/ServiceIdentity.java | 122 + .../yahoo/athenz/zms/ServiceIdentityList.java | 51 + .../yahoo/athenz/zms/ServicePrincipal.java | 59 + .../com/yahoo/athenz/zms/SignedDomain.java | 59 + .../com/yahoo/athenz/zms/SignedDomains.java | 38 + .../com/yahoo/athenz/zms/SignedPolicies.java | 60 + .../java/com/yahoo/athenz/zms/SubDomain.java | 144 + .../java/com/yahoo/athenz/zms/Template.java | 49 + .../com/yahoo/athenz/zms/TemplateList.java | 39 + .../java/com/yahoo/athenz/zms/Tenancy.java | 61 + .../athenz/zms/TenancyResourceGroup.java | 59 + .../com/yahoo/athenz/zms/TenantDomains.java | 38 + .../athenz/zms/TenantResourceGroupRoles.java | 83 + .../yahoo/athenz/zms/TenantRoleAction.java | 48 + .../com/yahoo/athenz/zms/TenantRoles.java | 71 + .../com/yahoo/athenz/zms/TopLevelDomain.java | 134 + .../java/com/yahoo/athenz/zms/UserDomain.java | 122 + .../java/com/yahoo/athenz/zms/UserToken.java | 37 + .../java/com/yahoo/athenz/zms/ZMSSchema.java | 1413 ++ core/zms/src/main/rdl/Access.rdli | 70 + core/zms/src/main/rdl/Domain.rdli | 233 + core/zms/src/main/rdl/Domain.tdl | 23 + core/zms/src/main/rdl/DomainDataCheck.rdli | 52 + core/zms/src/main/rdl/Entity.rdli | 66 + core/zms/src/main/rdl/Entity.tdl | 17 + core/zms/src/main/rdl/Names.tdl | 58 + core/zms/src/main/rdl/Policy.rdli | 136 + core/zms/src/main/rdl/Policy.tdl | 30 + core/zms/src/main/rdl/Provider.rdl | 12 + core/zms/src/main/rdl/Provider.rdli | 109 + core/zms/src/main/rdl/Role.rdli | 151 + core/zms/src/main/rdl/Role.tdl | 45 + core/zms/src/main/rdl/ServiceIdentity.rdli | 130 + core/zms/src/main/rdl/ServiceIdentity.tdl | 33 + core/zms/src/main/rdl/SignedDomains.rdli | 79 + core/zms/src/main/rdl/Template.rdli | 26 + core/zms/src/main/rdl/Template.tdl | 31 + core/zms/src/main/rdl/Tenancy.rdli | 101 + core/zms/src/main/rdl/TenantRoles.rdli | 192 + core/zms/src/main/rdl/Token.rdli | 52 + core/zms/src/main/rdl/ZMS.rdl | 23 + .../athenz/provider/ProviderSchemaTest.java | 155 + .../com/yahoo/athenz/zms/ZMSCoreTest.java | 1417 ++ core/zms/src/test/resources/zms_private.pem | 9 + core/zms/src/test/resources/zms_public.pem | 4 + core/zts/README.md | 12 + core/zts/pom.xml | 67 + core/zts/scripts/make_stubs.sh | 25 + .../athenz/zts/AWSCertificateRequest.java | 38 + .../athenz/zts/AWSInstanceInformation.java | 193 + .../athenz/zts/AWSTemporaryCredentials.java | 70 + .../java/com/yahoo/athenz/zts/Access.java | 37 + .../java/com/yahoo/athenz/zts/Assertion.java | 84 + .../com/yahoo/athenz/zts/AssertionEffect.java | 23 + .../com/yahoo/athenz/zts/DomainMetric.java | 48 + .../yahoo/athenz/zts/DomainMetricType.java | 39 + .../com/yahoo/athenz/zts/DomainMetrics.java | 49 + .../athenz/zts/DomainSignedPolicyData.java | 60 + .../com/yahoo/athenz/zts/HostServices.java | 50 + .../java/com/yahoo/athenz/zts/Identity.java | 100 + .../yahoo/athenz/zts/InstanceInformation.java | 94 + .../athenz/zts/InstanceRefreshRequest.java | 50 + .../java/com/yahoo/athenz/zts/Policy.java | 61 + .../java/com/yahoo/athenz/zts/PolicyData.java | 49 + .../com/yahoo/athenz/zts/PublicKeyEntry.java | 49 + .../java/com/yahoo/athenz/zts/RoleAccess.java | 38 + .../java/com/yahoo/athenz/zts/RoleToken.java | 48 + .../com/yahoo/athenz/zts/ServiceIdentity.java | 122 + .../yahoo/athenz/zts/ServiceIdentityList.java | 39 + .../yahoo/athenz/zts/SignedPolicyData.java | 82 + .../com/yahoo/athenz/zts/TenantDomains.java | 38 + .../java/com/yahoo/athenz/zts/ZTSSchema.java | 430 + core/zts/src/main/rdl/AWSAuth.rdli | 79 + core/zts/src/main/rdl/DomainMetrics.rdli | 50 + core/zts/src/main/rdl/Identity.rdli | 48 + core/zts/src/main/rdl/Identity.tdl | 15 + core/zts/src/main/rdl/Names.tdl | 53 + core/zts/src/main/rdl/Policy.tdl | 24 + core/zts/src/main/rdl/ServiceIdentity.rdli | 75 + core/zts/src/main/rdl/SignedPolicies.rdli | 41 + core/zts/src/main/rdl/Tenancy.rdli | 23 + core/zts/src/main/rdl/Token.rdli | 72 + core/zts/src/main/rdl/ZTS.rdl | 17 + .../athenz/zts/AWSCertificateRequestTest.java | 43 + .../zts/AWSInstanceInformationTest.java | 118 + .../zts/AWSTemporaryCredentialsTest.java | 63 + .../java/com/yahoo/athenz/zts/AccessTest.java | 52 + .../yahoo/athenz/zts/AssertionEffectTest.java | 35 + .../yahoo/athenz/zts/DomainMetricTest.java | 106 + .../zts/DomainSignedPolicyDataTest.java | 185 + .../yahoo/athenz/zts/HostServicesTest.java | 53 + .../com/yahoo/athenz/zts/IdentityTest.java | 79 + .../athenz/zts/InstanceInformationTest.java | 65 + .../zts/InstanceRefreshRequestTest.java | 47 + .../com/yahoo/athenz/zts/RoleAccessTest.java | 53 + .../com/yahoo/athenz/zts/RoleTokenTest.java | 47 + .../athenz/zts/ServiceIdentityListTest.java | 46 + .../yahoo/athenz/zts/ServiceIdentityTest.java | 112 + .../yahoo/athenz/zts/TenantDomainsTest.java | 56 + .../com/yahoo/athenz/zts/ZTSCoreTest.java | 89 + docs/auth_flow.md | 248 + docs/data_model.md | 290 + docs/dev_centralized_access.md | 123 + docs/dev_decentralized_access.md | 455 + docs/dev_environment.md | 58 + .../images/centralized_authz_for_services.png | Bin 0 -> 55521 bytes docs/images/centralized_authz_for_users.png | Bin 0 -> 58025 bytes docs/images/data_model.png | Bin 0 -> 38798 bytes .../decentralized_authz_for_services.png | Bin 0 -> 71142 bytes docs/images/system_view.png | Bin 0 -> 75727 bytes docs/images/use_case-service_auth.png | Bin 0 -> 58327 bytes docs/images/use_case-user_auth.png | Bin 0 -> 65390 bytes docs/reg_service_guide.md | 82 + docs/setup_zms.md | 93 + docs/setup_zts.md | 130 + docs/system_properties.md | 60 + docs/system_view.md | 75 + docs/zms_client.md | 314 + libs/go/zmscli/README.md | 11 + libs/go/zmscli/access.go | 70 + libs/go/zmscli/cli.go | 1524 ++ libs/go/zmscli/domain.go | 480 + libs/go/zmscli/dump.go | 449 + libs/go/zmscli/dump_test.go | 60 + libs/go/zmscli/entity.go | 76 + libs/go/zmscli/import.go | 212 + libs/go/zmscli/policy.go | 234 + libs/go/zmscli/policy_test.go | 118 + libs/go/zmscli/profile.go | 161 + libs/go/zmscli/repl.go | 49 + libs/go/zmscli/role.go | 209 + libs/go/zmscli/role_test.go | 28 + libs/go/zmscli/service.go | 362 + libs/go/zmscli/service_test.go | 23 + libs/go/zmscli/template.go | 82 + libs/go/zmscli/tenant.go | 206 + libs/go/zmscli/utils.go | 111 + libs/go/zmscli/utils_test.go | 170 + libs/java/auth_core/README.md | 10 + libs/java/auth_core/pom.xml | 98 + .../java/com/yahoo/athenz/auth/Authority.java | 89 + .../yahoo/athenz/auth/AuthorityKeyStore.java | 21 + .../com/yahoo/athenz/auth/Authorizer.java | 34 + .../java/com/yahoo/athenz/auth/KeyStore.java | 29 + .../java/com/yahoo/athenz/auth/Principal.java | 67 + .../athenz/auth/ServiceIdentityProvider.java | 31 + .../auth/impl/CertificateAuthority.java | 134 + .../athenz/auth/impl/KerberosAuthority.java | 352 + .../athenz/auth/impl/PrincipalAuthority.java | 319 + .../yahoo/athenz/auth/impl/RoleAuthority.java | 157 + .../athenz/auth/impl/SimplePrincipal.java | 233 + .../impl/SimpleServiceIdentityProvider.java | 106 + .../yahoo/athenz/auth/impl/UserAuthority.java | 142 + .../athenz/auth/token/KerberosToken.java | 131 + .../athenz/auth/token/PrincipalToken.java | 498 + .../yahoo/athenz/auth/token/RoleToken.java | 327 + .../com/yahoo/athenz/auth/token/Token.java | 205 + .../com/yahoo/athenz/auth/util/Crypto.java | 892 + .../athenz/auth/util/CryptoException.java | 86 + .../com/yahoo/athenz/auth/util/Validate.java | 50 + .../com/yahoo/athenz/auth/util/YBase64.java | 305 + .../auth/impl/CertificateAuthorityTest.java | 109 + .../auth/impl/KerberosAuthorityTest.java | 382 + .../yahoo/athenz/auth/impl/KeyStoreMock.java | 88 + .../athenz/auth/impl/MockPrivExcAction.java | 40 + .../auth/impl/PrincipalAuthorityTest.java | 598 + .../athenz/auth/impl/RoleAuthorityTest.java | 397 + .../athenz/auth/impl/SimplePrincipalTest.java | 253 + .../SimpleServiceIdentityProviderTest.java | 132 + .../auth/impl/TestLoginCallbackHandler.java | 75 + .../athenz/auth/impl/UserAuthorityTest.java | 86 + .../athenz/auth/token/PrincipalTokenTest.java | 724 + .../athenz/auth/token/RoleTokenTest.java | 478 + .../yahoo/athenz/auth/token/TokenTest.java | 181 + .../yahoo/athenz/auth/util/CryptoTest.java | 516 + .../yahoo/athenz/auth/util/ValidateTest.java | 78 + .../yahoo/athenz/auth/util/YBase64Test.java | 160 + .../auth_core/src/test/resources/arg_file | 1 + .../src/test/resources/aws_public.crt | 18 + .../src/test/resources/csr_altnames.csr | 19 + .../src/test/resources/ec_private.key | 5 + .../resources/ec_private_param_prime256v1.key | 8 + .../resources/ec_private_param_secp384r1.key | 9 + .../src/test/resources/ec_private_params.key | 13 + .../src/test/resources/ec_public.key | 4 + .../src/test/resources/ec_public_invalid.key | 4 + .../resources/ec_public_param_prime256v1.key | 7 + .../resources/ec_public_param_secp384r1.key | 8 + .../src/test/resources/ec_public_params.key | 12 + .../src/test/resources/ec_public_x509.cert | 16 + .../src/test/resources/example.keytab | Bin 0 -> 118 bytes .../src/test/resources/fantasy_private_k0.key | 15 + .../src/test/resources/fantasy_private_k1.key | 9 + .../src/test/resources/fantasy_public_k0.key | 6 + .../src/test/resources/fantasy_public_k1.key | 4 + .../src/test/resources/host_private.key | 9 + .../src/test/resources/host_public.key | 4 + .../auth_core/src/test/resources/invalid.csr | 12 + .../src/test/resources/invalid_x509.cert | 16 + .../auth_core/src/test/resources/jaas.conf | 14 + .../auth_core/src/test/resources/logback.xml | 17 + .../src/test/resources/private_encrypted.key | 10 + .../src/test/resources/rsa_private.key | 9 + .../src/test/resources/rsa_public.key | 4 + .../src/test/resources/rsa_public_invalid.key | 4 + .../src/test/resources/rsa_public_x509.cert | 16 + .../auth_core/src/test/resources/valid.csr | 8 + .../src/test/resources/valid_cn_x509.cert | 16 + .../src/test/resources/zts_private_k0.key | 15 + .../src/test/resources/zts_private_k1.key | 9 + .../src/test/resources/zts_public_k0.key | 6 + .../src/test/resources/zts_public_k1.key | 4 + libs/java/client_common/README.md | 12 + libs/java/client_common/pom.xml | 43 + .../athenz/common/config/AthenzConfig.java | 54 + .../yahoo/athenz/common/metrics/Metric.java | 77 + .../athenz/common/metrics/MetricFactory.java | 26 + .../common/metrics/impl/NoOpMetric.java | 57 + .../metrics/impl/NoOpMetricFactory.java | 27 + .../yahoo/athenz/common/utils/SignUtils.java | 286 + .../common/config/AthenzConfigTest.java | 65 + .../common/metrics/impl/MetricsTest.java | 35 + .../athenz/common/utils/SignUtilsTest.java | 165 + .../src/test/resources/logback.xml | 17 + libs/java/server_common/README.md | 15 + libs/java/server_common/pom.xml | 108 + .../server/debug/DebugKerberosAuthority.java | 82 + .../server/debug/DebugPrincipalAuthority.java | 94 + .../server/debug/DebugRoleAuthority.java | 92 + .../server/debug/DebugUserAuthority.java | 102 + .../filters/DefaultMediaTypeFilter.java | 45 + .../common/server/filters/ETagFilter.java | 66 + .../common/server/log/AthenzRequestLog.java | 133 + .../common/server/log/AuditLogFactory.java | 96 + .../common/server/log/AuditLogMsgBuilder.java | 166 + .../athenz/common/server/log/AuditLogger.java | 38 + .../log/impl/DefaultAuditLogMsgBuilder.java | 683 + .../server/log/impl/DefaultAuditLogger.java | 61 + .../yahoo/athenz/common/server/rest/Http.java | 175 + .../common/server/rest/HttpContainer.java | 202 + .../common/server/rest/ResourceContext.java | 69 + .../common/server/rest/ResourceException.java | 164 + .../server/rest/RestCoreResourceConfig.java | 123 + .../server/util/ServletRequestUtil.java | 55 + .../common/server/util/StringUtils.java | 67 + .../debug/DebugKerberosAuthorityTest.java | 95 + .../debug/DebugPrincipalAuthorityTest.java | 57 + .../server/debug/DebugRoleAuthorityTest.java | 65 + .../server/debug/DebugUserAuthorityTest.java | 52 + .../common/server/filters/ETagFilterTest.java | 64 + .../filters/MockHttpServletRequest.java | 398 + .../filters/MockHttpServletResponse.java | 215 + .../server/log/AthenzRequestLogTest.java | 216 + .../log/impl/AuditLogMsgBuilderTest.java | 664 + .../server/log/impl/AuditLoggerTest.java | 106 + .../common/server/log/impl/TestLogger.java | 57 + .../server/log/impl/TestMsgBuilder.java | 24 + .../common/server/rest/HttpContainerTest.java | 77 + .../athenz/common/server/rest/HttpTest.java | 147 + .../server/rest/ResourceContextTest.java | 66 + .../server/rest/ResourceExceptionTest.java | 81 + .../rest/RestCoreResourceConfigTest.java | 56 + .../server/util/ServletRequestUtilTest.java | 65 + .../common/server/util/StringUtilsTest.java | 48 + .../src/test/resources/logback.xml | 17 + pom.xml | 193 + servers/zms/README.md | 14 + servers/zms/conf/authorized_services.json | 37 + servers/zms/conf/container_settings | 78 + servers/zms/conf/logback.xml | 49 + servers/zms/conf/solution_templates.json | 61 + servers/zms/pom.xml | 226 + servers/zms/schema/zms_server.mwb | Bin 0 -> 22654 bytes servers/zms/schema/zms_server.sql | 266 + servers/zms/scripts/make_stubs.sh | 31 + servers/zms/scripts/zms_debug.sh | 37 + servers/zms/scripts/zms_start.sh | 196 + .../yahoo/athenz/provider/ProviderClient.java | 154 + .../yahoo/athenz/provider/ResourceError.java | 24 + .../athenz/provider/ResourceException.java | 79 + .../java/com/yahoo/athenz/zms/DBService.java | 2579 +++ .../athenz/zms/GetSignedDomainsResult.java | 48 + .../com/yahoo/athenz/zms/ResourceContext.java | 17 + .../com/yahoo/athenz/zms/ResourceError.java | 24 + .../yahoo/athenz/zms/ResourceException.java | 79 + .../main/java/com/yahoo/athenz/zms/ZMS.java | 241 + .../java/com/yahoo/athenz/zms/ZMSConsts.java | 184 + .../java/com/yahoo/athenz/zms/ZMSDaemon.java | 40 + .../java/com/yahoo/athenz/zms/ZMSHandler.java | 82 + .../java/com/yahoo/athenz/zms/ZMSImpl.java | 6208 +++++++ .../yahoo/athenz/zms/ZMSJettyContainer.java | 275 + .../com/yahoo/athenz/zms/ZMSResources.java | 1843 ++ .../com/yahoo/athenz/zms/ZMSServerImpl.java | 109 + .../athenz/zms/config/AllowedOperation.java | 94 + .../athenz/zms/config/AuthorizedService.java | 34 + .../athenz/zms/config/AuthorizedServices.java | 46 + .../athenz/zms/config/SolutionTemplates.java | 47 + .../athenz/zms/pkey/PrivateKeyStore.java | 41 + .../zms/pkey/PrivateKeyStoreFactory.java | 26 + .../zms/pkey/file/FilePrivateKeyStore.java | 131 + .../pkey/file/FilePrivateKeyStoreFactory.java | 27 + .../yahoo/athenz/zms/store/AthenzDomain.java | 80 + .../yahoo/athenz/zms/store/ObjectStore.java | 22 + .../zms/store/ObjectStoreConnection.java | 117 + .../athenz/zms/store/file/DomainStruct.java | 96 + .../athenz/zms/store/file/FileConnection.java | 1197 ++ .../zms/store/file/FileObjectStore.java | 52 + .../zms/store/jdbc/DataSourceFactory.java | 267 + .../athenz/zms/store/jdbc/JDBCConnection.java | 2987 ++++ .../zms/store/jdbc/JDBCObjectStore.java | 47 + .../zms/store/jdbc/PoolableDataSource.java | 26 + .../athenz/zms/store/jdbc/ZMSDataSource.java | 46 + .../com/yahoo/athenz/zms/utils/ZMSUtils.java | 216 + servers/zms/src/main/rdl/ZMS.rdl | 17 + .../athenz/provider/ProviderMockClient.java | 96 + .../provider/ProviderMockClientTest.java | 50 + .../com/yahoo/athenz/zms/DBServiceTest.java | 2498 +++ .../athenz/zms/MockConnectionFactory.java | 30 + .../athenz/zms/MockHttpServletRequest.java | 462 + .../athenz/zms/MockHttpServletResponse.java | 235 + .../athenz/zms/ResourceExceptionTest.java | 72 + .../com/yahoo/athenz/zms/ZMSDaemonTest.java | 54 + .../com/yahoo/athenz/zms/ZMSImplTest.java | 14109 ++++++++++++++++ .../athenz/zms/ZMSJettyContainerTest.java | 425 + .../yahoo/athenz/zms/ZMSServerImplTest.java | 112 + .../java/com/yahoo/athenz/zms/ZMSTest.java | 192 + .../zms/config/AllowedOperationTest.java | 303 + .../zms/config/AuthorizedServicesTest.java | 50 + .../zms/config/SolutionTemplatesTest.java | 31 + .../athenz/zms/pkey/PrivateKeyStoreTest.java | 42 + .../pkey/file/FilePrivateKeyStoreTest.java | 138 + .../zms/store/file/FileConnectionTest.java | 514 + .../zms/store/file/FileObjectStoreTest.java | 49 + .../zms/store/jdbc/DataSourceFactoryTest.java | 320 + .../zms/store/jdbc/JDBCConnectionTest.java | 5263 ++++++ .../zms/store/jdbc/JDBCObjectStoreTest.java | 53 + .../yahoo/athenz/zms/utils/ZMSUtilsTest.java | 147 + .../test/resources/authorized_services.json | 38 + servers/zms/src/test/resources/logback.xml | 58 + .../test/resources/solution_templates.json | 234 + .../zms/src/test/resources/zms_private.pem | 9 + .../zms/src/test/resources/zms_private_k1.pem | 15 + .../zms/src/test/resources/zms_private_k2.pem | 15 + servers/zms/src/test/resources/zms_public.pem | 4 + .../zms/src/test/resources/zms_public_k1.pem | 6 + .../zms/src/test/resources/zms_public_k2.pem | 6 + servers/zts/README.md | 12 + servers/zts/conf/aws_public.crt | 18 + servers/zts/conf/container_settings | 73 + servers/zts/conf/logback.xml | 49 + servers/zts/pom.xml | 188 + servers/zts/scripts/make_stubs.sh | 27 + servers/zts/scripts/zts_debug.sh | 31 + servers/zts/scripts/zts_start.sh | 190 + .../zts/GetDomainSignedPolicyDataResult.java | 49 + .../com/yahoo/athenz/zts/ResourceContext.java | 17 + .../com/yahoo/athenz/zts/ResourceError.java | 24 + .../yahoo/athenz/zts/ResourceException.java | 79 + .../com/yahoo/athenz/zts/RsrcCtxWrapper.java | 111 + .../main/java/com/yahoo/athenz/zts/ZTS.java | 393 + .../com/yahoo/athenz/zts/ZTSAuthorizer.java | 379 + .../java/com/yahoo/athenz/zts/ZTSBinder.java | 39 + .../java/com/yahoo/athenz/zts/ZTSConsts.java | 102 + .../java/com/yahoo/athenz/zts/ZTSDaemon.java | 40 + .../java/com/yahoo/athenz/zts/ZTSHandler.java | 32 + .../java/com/yahoo/athenz/zts/ZTSImpl.java | 1609 ++ .../yahoo/athenz/zts/ZTSJettyContainer.java | 225 + .../com/yahoo/athenz/zts/ZTSResources.java | 428 + .../com/yahoo/athenz/zts/cache/DataCache.java | 308 + .../athenz/zts/cache/DataCacheProvider.java | 22 + .../com/yahoo/athenz/zts/cert/CertSigner.java | 39 + .../athenz/zts/cert/CertSignerFactory.java | 27 + .../yahoo/athenz/zts/cert/SvcCertStore.java | 38 + .../athenz/zts/cert/SvcCertStoreFactory.java | 26 + .../athenz/zts/cert/X509CertSignObject.java | 36 + .../athenz/zts/cert/impl/YCertSigner.java | 158 + .../zts/cert/impl/YCertSignerFactory.java | 29 + .../athenz/zts/cert/impl/YSvcCertStore.java | 45 + .../zts/cert/impl/YSvcCertStoreFactory.java | 31 + .../athenz/zts/pkey/PrivateKeyStore.java | 41 + .../zts/pkey/PrivateKeyStoreFactory.java | 26 + .../zts/pkey/file/FilePrivateKeyStore.java | 128 + .../pkey/file/FilePrivateKeyStoreFactory.java | 27 + .../zts/pkey/hsm/HSMPrivateKeyStore.java | 31 + .../pkey/hsm/HSMPrivateKeyStoreFactory.java | 27 + .../athenz/zts/store/ChangeLogStore.java | 87 + .../zts/store/ChangeLogStoreFactory.java | 32 + .../yahoo/athenz/zts/store/CloudStore.java | 636 + .../com/yahoo/athenz/zts/store/DataStore.java | 925 + .../zts/store/file/ZMSFileChangeLogStore.java | 348 + .../file/ZMSFileChangeLogStoreFactory.java | 35 + .../athenz/zts/store/s3/S3ChangeLogStore.java | 234 + .../zts/store/s3/S3ChangeLogStoreFactory.java | 33 + .../com/yahoo/athenz/zts/utils/ZTSUtils.java | 221 + servers/zts/src/main/rdl/ZTS.rdl | 17 + .../athenz/zts/ResourceExceptionTest.java | 69 + .../yahoo/athenz/zts/ResultObjectTest.java | 58 + .../yahoo/athenz/zts/RsrcCtxWrapperTest.java | 131 + .../com/yahoo/athenz/zts/ZTSBinderTest.java | 39 + .../com/yahoo/athenz/zts/ZTSDaemonTest.java | 98 + .../com/yahoo/athenz/zts/ZTSImplTest.java | 3662 ++++ .../athenz/zts/ZTSJettyContainerTest.java | 331 + .../java/com/yahoo/athenz/zts/ZTSTest.java | 219 + .../yahoo/athenz/zts/cache/DataCacheTest.java | 862 + .../yahoo/athenz/zts/cert/MockCertSigner.java | 51 + .../zts/cert/MockCertSignerFactory.java | 38 + .../zts/cert/X509CertSignObjectTest.java | 42 + .../athenz/zts/cert/impl/YCertSignerTest.java | 257 + .../pkey/file/FilePrivateKeyStoreTest.java | 145 + .../zts/pkey/hsm/HSMPrivateKeyStoreTest.java | 46 + .../athenz/zts/store/CloudStoreTest.java | 928 + .../yahoo/athenz/zts/store/DataStoreTest.java | 3425 ++++ .../athenz/zts/store/MockCloudStore.java | 148 + .../store/file/MockZMSFileChangeLogStore.java | 104 + .../MockZMSFileChangeLogStoreFactory.java | 31 + .../ZMSFileChangeLogStoreFactoryTest.java | 46 + .../store/file/ZMSFileChangeLogStoreTest.java | 219 + .../zts/store/s3/MockS3ChangeLogStore.java | 38 + .../store/s3/MockS3ChangeLogStoreFactory.java | 31 + .../store/s3/S3ChangeLogStoreFactoryTest.java | 33 + .../zts/store/s3/S3ChangeLogStoreTest.java | 457 + .../yahoo/athenz/zts/utils/ZTSUtilsTest.java | 164 + servers/zts/src/test/resources/athenz.conf | 20 + servers/zts/src/test/resources/iaas.json | 1 + servers/zts/src/test/resources/logback.xml | 57 + .../src/test/resources/private_encrypted.key | 10 + .../zts/src/test/resources/test_private.v1 | 9 + servers/zts/src/test/resources/test_public.v1 | 4 + servers/zts/src/test/resources/valid.csr | 8 + .../zts/src/test/resources/valid_cn_x509.cert | 16 + .../zts/src/test/resources/zts_private.pem | 9 + servers/zts/src/test/resources/zts_public.pem | 4 + utils/zms_cli/README.md | 11 + utils/zms_cli/zms-cli.go | 312 + utils/zpe_policy_updater/README.md | 9 + utils/zpe_policy_updater/conf/logback.xml | 29 + .../zpe_policy_updater/conf/utility_settings | 13 + utils/zpe_policy_updater/conf/zpu.conf | 3 + utils/zpe_policy_updater/pom.xml | 91 + utils/zpe_policy_updater/scripts/zpu_run.sh | 33 + .../scripts/zpu_set_ownership.pl | 42 + .../zpe_policy_updater/PolicyUpdater.java | 423 + .../PolicyUpdaterConfiguration.java | 277 + .../zpe_policy_updater/SIAClientFactory.java | 26 + .../SIAClientFactoryImpl.java | 28 + .../zpe_policy_updater/ZTSClientFactory.java | 25 + .../ZTSClientFactoryImpl.java | 26 + .../DebugSIAClientFactory.java | 39 + .../DebugZTSClientFactory.java | 39 + .../zpe_policy_updater/MockMetricFactory.java | 94 + .../zpe_policy_updater/PolicyUpdaterTest.java | 524 + .../zpe_policy_updater/SIAClientMock.java | 103 + .../SignPoliciesUtility.java | 94 + .../athenz/zpe_policy_updater/ZTSMock.java | 238 + .../src/test/resources/athenz.conf | 24 + .../src/test/resources/logback.xml | 17 + .../src/test/resources/pol_dir/.keepme | 0 .../src/test/resources/sports.pol | 28 + .../src/test/resources/sys.auth.pol | 32 + .../test/resources/sys.auth.pol.tampered.zms | 32 + .../test/resources/sys.auth.pol.tampered.zts | 32 + .../src/test/resources/tmp/.keepme | 0 .../src/test/resources/zms_private_k0.pem | 9 + .../src/test/resources/zms_public_k0.pem | 4 + .../src/test/resources/zpu.conf | 4 + .../src/test/resources/zpu_empty.conf | 3 + .../src/test/resources/zpu_private.pem | 9 + .../src/test/resources/zpu_public.pem | 4 + .../src/test/resources/zts_private_k0.pem | 9 + .../src/test/resources/zts_private_k1.pem | 9 + .../src/test/resources/zts_public_k0.pem | 4 + .../src/test/resources/zts_public_k1.pem | 4 + 634 files changed, 129974 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 assembly/components/zms.xml create mode 100644 assembly/components/zpu.xml create mode 100644 assembly/components/zts.xml create mode 100644 assembly/pom.xml create mode 100644 clients/go/zms/Makefile create mode 100644 clients/go/zms/README.md create mode 100644 clients/go/zms/auth.go create mode 100644 clients/go/zms/client.go create mode 100644 clients/go/zms/model.go create mode 100644 clients/go/zms/zms_schema.go create mode 100644 clients/go/zts/Makefile create mode 100644 clients/go/zts/README.md create mode 100644 clients/go/zts/client.go create mode 100644 clients/go/zts/model.go create mode 100644 clients/go/zts/zts_schema.go create mode 100644 clients/java/sia/README.md create mode 100644 clients/java/sia/pom.xml create mode 100644 clients/java/sia/src/main/java/com/yahoo/athenz/sia/SIA.java create mode 100644 clients/java/sia/src/main/java/com/yahoo/athenz/sia/impl/SIAClient.java create mode 100644 clients/java/sia/src/test/java/com/yahoo/athenz/sia/impl/SIAClientTest.java create mode 100644 clients/java/sia/src/test/resources/dummy.ntoken create mode 100644 clients/java/sia/src/test/resources/logback.xml create mode 100644 clients/java/sia/src/test/resources/svc.ntoken_test create mode 100644 clients/java/sia/src/test/resources/testcfg.conf create mode 100644 clients/java/sia/src/test/resources/testcfg_empty.conf create mode 100644 clients/java/zms/README.md create mode 100644 clients/java/zms/pom.xml create mode 100755 clients/java/zms/scripts/make_stubs.sh create mode 100644 clients/java/zms/src/main/java/com/yahoo/athenz/zms/ResourceError.java create mode 100644 clients/java/zms/src/main/java/com/yahoo/athenz/zms/ResourceException.java create mode 100644 clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSAuthorizer.java create mode 100644 clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClient.java create mode 100644 clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClientException.java create mode 100644 clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSRDLGeneratedClient.java create mode 100644 clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.core.JsonFactory create mode 100644 clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.core.ObjectCodec create mode 100644 clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.databind.Module create mode 100644 clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.client.ClientBuilder create mode 100644 clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.MessageBodyReader create mode 100644 clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.MessageBodyWriter create mode 100644 clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.RuntimeDelegate create mode 100644 clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.org.glassfish.hk2.extension.ServiceLocatorGenerator create mode 100644 clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.org.glassfish.jersey.internal.spi.AutoDiscoverable create mode 100644 clients/java/zms/src/test/java/com/yahoo/athenz/zms/ResourceExceptionTest.java create mode 100644 clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSAuthorizerTest.java create mode 100644 clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientMockTest.java create mode 100644 clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientTest.java create mode 100644 clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSLoadData.java create mode 100644 clients/java/zms/src/test/resources/athenz.conf create mode 100644 clients/java/zms/src/test/resources/logback.xml create mode 100644 clients/java/zpe/README.md create mode 100644 clients/java/zpe/pom.xml create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/AuthZpeClient.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeClient.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeConsts.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeMetric.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeThreadFactory.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdMonitor.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdPolLoader.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdater.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeYcrKey.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/ZpeMatch.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchAll.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchEqual.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchRegex.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchStartsWith.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/PublicKeyStore.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/PublicKeyStoreFactory.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/file/FilePublicKeyStore.java create mode 100644 clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/file/FilePublicKeyStoreFactory.java create mode 100644 clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/MockMetricFactory.java create mode 100644 clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestAuthZpe.java create mode 100644 clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeMatch.java create mode 100644 clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeMetric.java create mode 100644 clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeUpdPolLoader.java create mode 100644 clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeYcrKey.java create mode 100644 clients/java/zpe/src/test/resources/angler.pol create mode 100755 clients/java/zpe/src/test/resources/athenz.conf create mode 100755 clients/java/zpe/src/test/resources/athenz_no_0.conf create mode 100644 clients/java/zpe/src/test/resources/empty.pol create mode 100644 clients/java/zpe/src/test/resources/logback.xml create mode 100644 clients/java/zpe/src/test/resources/logback_perf.xml create mode 100644 clients/java/zpe/src/test/resources/pol_dir/.keepme create mode 100644 clients/java/zpe/src/test/resources/sports.pol create mode 100644 clients/java/zpe/src/test/resources/sys.auth.pol create mode 100644 clients/java/zpe/src/test/resources/zms_private_k0.pem create mode 100644 clients/java/zpe/src/test/resources/zms_public_k0.pem create mode 100644 clients/java/zpe/src/test/resources/zts_private_k0.pem create mode 100644 clients/java/zpe/src/test/resources/zts_private_k1.pem create mode 100644 clients/java/zpe/src/test/resources/zts_private_k17.pem create mode 100644 clients/java/zpe/src/test/resources/zts_private_k99.pem create mode 100644 clients/java/zpe/src/test/resources/zts_public_k0.pem create mode 100644 clients/java/zpe/src/test/resources/zts_public_k1.pem create mode 100644 clients/java/zpe/src/test/resources/zts_public_k17.pem create mode 100644 clients/java/zpe/src/test/resources/zts_public_k99.pem create mode 100644 clients/java/zts/README.md create mode 100644 clients/java/zts/pom.xml create mode 100755 clients/java/zts/scripts/make_stubs.sh create mode 100644 clients/java/zts/src/main/java/com/yahoo/athenz/zts/ResourceError.java create mode 100644 clients/java/zts/src/main/java/com/yahoo/athenz/zts/ResourceException.java create mode 100644 clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClient.java create mode 100644 clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientException.java create mode 100644 clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientService.java create mode 100644 clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientTokenCacher.java create mode 100644 clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClient.java create mode 100644 clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.core.JsonFactory create mode 100644 clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.core.ObjectCodec create mode 100644 clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.databind.Module create mode 100644 clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.client.ClientBuilder create mode 100644 clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.MessageBodyReader create mode 100644 clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.MessageBodyWriter create mode 100644 clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.RuntimeDelegate create mode 100644 clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.org.glassfish.hk2.extension.ServiceLocatorGenerator create mode 100644 clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.org.glassfish.jersey.internal.spi.AutoDiscoverable create mode 100644 clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientMock.java create mode 100644 clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientServiceProvider.java create mode 100644 clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientTest.java create mode 100644 clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClientMock.java create mode 100644 clients/java/zts/src/test/resources/META-INF/services/com.yahoo.athenz.zts.ZTSClientService create mode 100644 clients/java/zts/src/test/resources/athenz.conf create mode 100644 core/zms/README.md create mode 100644 core/zms/pom.xml create mode 100755 core/zms/scripts/make_stubs.sh create mode 100644 core/zms/src/main/java/com/yahoo/athenz/provider/ProviderSchema.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/provider/Tenant.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/provider/TenantResourceGroup.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/provider/TenantState.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/Access.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/Assertion.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/AssertionEffect.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/DanglingPolicy.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/DefaultAdmins.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/Domain.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/DomainData.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/DomainDataCheck.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/DomainList.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/DomainMeta.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/DomainModified.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/DomainModifiedList.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/DomainPolicies.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/DomainTemplate.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/DomainTemplateList.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/Entity.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/EntityList.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/HostServices.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/Membership.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/Policies.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/Policy.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/PolicyList.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/ProviderResourceGroupRoles.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/PublicKeyEntry.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/ResourceAccess.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/ResourceAccessList.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/Role.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/RoleAuditLog.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/RoleList.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/Roles.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/ServerTemplateList.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentities.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentity.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentityList.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/ServicePrincipal.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/SignedDomain.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/SignedDomains.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/SignedPolicies.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/SubDomain.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/Template.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/TemplateList.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/Tenancy.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/TenancyResourceGroup.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/TenantDomains.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/TenantResourceGroupRoles.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/TenantRoleAction.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/TenantRoles.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/TopLevelDomain.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/UserDomain.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/UserToken.java create mode 100644 core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java create mode 100644 core/zms/src/main/rdl/Access.rdli create mode 100644 core/zms/src/main/rdl/Domain.rdli create mode 100644 core/zms/src/main/rdl/Domain.tdl create mode 100644 core/zms/src/main/rdl/DomainDataCheck.rdli create mode 100644 core/zms/src/main/rdl/Entity.rdli create mode 100644 core/zms/src/main/rdl/Entity.tdl create mode 100644 core/zms/src/main/rdl/Names.tdl create mode 100644 core/zms/src/main/rdl/Policy.rdli create mode 100644 core/zms/src/main/rdl/Policy.tdl create mode 100644 core/zms/src/main/rdl/Provider.rdl create mode 100644 core/zms/src/main/rdl/Provider.rdli create mode 100644 core/zms/src/main/rdl/Role.rdli create mode 100644 core/zms/src/main/rdl/Role.tdl create mode 100644 core/zms/src/main/rdl/ServiceIdentity.rdli create mode 100644 core/zms/src/main/rdl/ServiceIdentity.tdl create mode 100644 core/zms/src/main/rdl/SignedDomains.rdli create mode 100644 core/zms/src/main/rdl/Template.rdli create mode 100644 core/zms/src/main/rdl/Template.tdl create mode 100644 core/zms/src/main/rdl/Tenancy.rdli create mode 100644 core/zms/src/main/rdl/TenantRoles.rdli create mode 100644 core/zms/src/main/rdl/Token.rdli create mode 100644 core/zms/src/main/rdl/ZMS.rdl create mode 100644 core/zms/src/test/java/com/yahoo/athenz/provider/ProviderSchemaTest.java create mode 100644 core/zms/src/test/java/com/yahoo/athenz/zms/ZMSCoreTest.java create mode 100644 core/zms/src/test/resources/zms_private.pem create mode 100644 core/zms/src/test/resources/zms_public.pem create mode 100644 core/zts/README.md create mode 100644 core/zts/pom.xml create mode 100755 core/zts/scripts/make_stubs.sh create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/AWSCertificateRequest.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/AWSInstanceInformation.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/AWSTemporaryCredentials.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/Access.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/Assertion.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/AssertionEffect.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetric.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetricType.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetrics.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/DomainSignedPolicyData.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/HostServices.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/Identity.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/InstanceInformation.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/InstanceRefreshRequest.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/Policy.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/PolicyData.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/PublicKeyEntry.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/RoleAccess.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/RoleToken.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/ServiceIdentity.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/ServiceIdentityList.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/SignedPolicyData.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/TenantDomains.java create mode 100644 core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java create mode 100644 core/zts/src/main/rdl/AWSAuth.rdli create mode 100644 core/zts/src/main/rdl/DomainMetrics.rdli create mode 100644 core/zts/src/main/rdl/Identity.rdli create mode 100644 core/zts/src/main/rdl/Identity.tdl create mode 100644 core/zts/src/main/rdl/Names.tdl create mode 100644 core/zts/src/main/rdl/Policy.tdl create mode 100644 core/zts/src/main/rdl/ServiceIdentity.rdli create mode 100644 core/zts/src/main/rdl/SignedPolicies.rdli create mode 100644 core/zts/src/main/rdl/Tenancy.rdli create mode 100644 core/zts/src/main/rdl/Token.rdli create mode 100644 core/zts/src/main/rdl/ZTS.rdl create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/AWSCertificateRequestTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/AWSInstanceInformationTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/AWSTemporaryCredentialsTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/AccessTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/AssertionEffectTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/DomainMetricTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/DomainSignedPolicyDataTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/HostServicesTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/IdentityTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/InstanceInformationTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/InstanceRefreshRequestTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/RoleAccessTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/RoleTokenTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/ServiceIdentityListTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/ServiceIdentityTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/TenantDomainsTest.java create mode 100644 core/zts/src/test/java/com/yahoo/athenz/zts/ZTSCoreTest.java create mode 100644 docs/auth_flow.md create mode 100644 docs/data_model.md create mode 100644 docs/dev_centralized_access.md create mode 100644 docs/dev_decentralized_access.md create mode 100644 docs/dev_environment.md create mode 100644 docs/images/centralized_authz_for_services.png create mode 100644 docs/images/centralized_authz_for_users.png create mode 100644 docs/images/data_model.png create mode 100644 docs/images/decentralized_authz_for_services.png create mode 100644 docs/images/system_view.png create mode 100644 docs/images/use_case-service_auth.png create mode 100644 docs/images/use_case-user_auth.png create mode 100644 docs/reg_service_guide.md create mode 100644 docs/setup_zms.md create mode 100644 docs/setup_zts.md create mode 100644 docs/system_properties.md create mode 100644 docs/system_view.md create mode 100644 docs/zms_client.md create mode 100644 libs/go/zmscli/README.md create mode 100644 libs/go/zmscli/access.go create mode 100644 libs/go/zmscli/cli.go create mode 100644 libs/go/zmscli/domain.go create mode 100644 libs/go/zmscli/dump.go create mode 100644 libs/go/zmscli/dump_test.go create mode 100644 libs/go/zmscli/entity.go create mode 100644 libs/go/zmscli/import.go create mode 100644 libs/go/zmscli/policy.go create mode 100644 libs/go/zmscli/policy_test.go create mode 100644 libs/go/zmscli/profile.go create mode 100644 libs/go/zmscli/repl.go create mode 100644 libs/go/zmscli/role.go create mode 100644 libs/go/zmscli/role_test.go create mode 100644 libs/go/zmscli/service.go create mode 100644 libs/go/zmscli/service_test.go create mode 100644 libs/go/zmscli/template.go create mode 100644 libs/go/zmscli/tenant.go create mode 100644 libs/go/zmscli/utils.go create mode 100644 libs/go/zmscli/utils_test.go create mode 100644 libs/java/auth_core/README.md create mode 100644 libs/java/auth_core/pom.xml create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Authority.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/AuthorityKeyStore.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Authorizer.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/KeyStore.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Principal.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/ServiceIdentityProvider.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/CertificateAuthority.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/KerberosAuthority.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/PrincipalAuthority.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/RoleAuthority.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/SimplePrincipal.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/SimpleServiceIdentityProvider.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/UserAuthority.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/KerberosToken.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/PrincipalToken.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/RoleToken.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/Token.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/Crypto.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/CryptoException.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/Validate.java create mode 100644 libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/YBase64.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/CertificateAuthorityTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/KerberosAuthorityTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/KeyStoreMock.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/MockPrivExcAction.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/PrincipalAuthorityTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/RoleAuthorityTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/SimplePrincipalTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/SimpleServiceIdentityProviderTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/TestLoginCallbackHandler.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/UserAuthorityTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/PrincipalTokenTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/RoleTokenTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/TokenTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/CryptoTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/ValidateTest.java create mode 100644 libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/YBase64Test.java create mode 100644 libs/java/auth_core/src/test/resources/arg_file create mode 100644 libs/java/auth_core/src/test/resources/aws_public.crt create mode 100644 libs/java/auth_core/src/test/resources/csr_altnames.csr create mode 100644 libs/java/auth_core/src/test/resources/ec_private.key create mode 100644 libs/java/auth_core/src/test/resources/ec_private_param_prime256v1.key create mode 100644 libs/java/auth_core/src/test/resources/ec_private_param_secp384r1.key create mode 100644 libs/java/auth_core/src/test/resources/ec_private_params.key create mode 100644 libs/java/auth_core/src/test/resources/ec_public.key create mode 100644 libs/java/auth_core/src/test/resources/ec_public_invalid.key create mode 100644 libs/java/auth_core/src/test/resources/ec_public_param_prime256v1.key create mode 100644 libs/java/auth_core/src/test/resources/ec_public_param_secp384r1.key create mode 100644 libs/java/auth_core/src/test/resources/ec_public_params.key create mode 100644 libs/java/auth_core/src/test/resources/ec_public_x509.cert create mode 100644 libs/java/auth_core/src/test/resources/example.keytab create mode 100644 libs/java/auth_core/src/test/resources/fantasy_private_k0.key create mode 100644 libs/java/auth_core/src/test/resources/fantasy_private_k1.key create mode 100644 libs/java/auth_core/src/test/resources/fantasy_public_k0.key create mode 100644 libs/java/auth_core/src/test/resources/fantasy_public_k1.key create mode 100644 libs/java/auth_core/src/test/resources/host_private.key create mode 100644 libs/java/auth_core/src/test/resources/host_public.key create mode 100644 libs/java/auth_core/src/test/resources/invalid.csr create mode 100644 libs/java/auth_core/src/test/resources/invalid_x509.cert create mode 100644 libs/java/auth_core/src/test/resources/jaas.conf create mode 100644 libs/java/auth_core/src/test/resources/logback.xml create mode 100644 libs/java/auth_core/src/test/resources/private_encrypted.key create mode 100644 libs/java/auth_core/src/test/resources/rsa_private.key create mode 100644 libs/java/auth_core/src/test/resources/rsa_public.key create mode 100644 libs/java/auth_core/src/test/resources/rsa_public_invalid.key create mode 100644 libs/java/auth_core/src/test/resources/rsa_public_x509.cert create mode 100644 libs/java/auth_core/src/test/resources/valid.csr create mode 100644 libs/java/auth_core/src/test/resources/valid_cn_x509.cert create mode 100644 libs/java/auth_core/src/test/resources/zts_private_k0.key create mode 100644 libs/java/auth_core/src/test/resources/zts_private_k1.key create mode 100644 libs/java/auth_core/src/test/resources/zts_public_k0.key create mode 100644 libs/java/auth_core/src/test/resources/zts_public_k1.key create mode 100644 libs/java/client_common/README.md create mode 100644 libs/java/client_common/pom.xml create mode 100644 libs/java/client_common/src/main/java/com/yahoo/athenz/common/config/AthenzConfig.java create mode 100644 libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/Metric.java create mode 100644 libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/MetricFactory.java create mode 100644 libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/impl/NoOpMetric.java create mode 100644 libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/impl/NoOpMetricFactory.java create mode 100644 libs/java/client_common/src/main/java/com/yahoo/athenz/common/utils/SignUtils.java create mode 100644 libs/java/client_common/src/test/java/com/yahoo/athenz/common/config/AthenzConfigTest.java create mode 100644 libs/java/client_common/src/test/java/com/yahoo/athenz/common/metrics/impl/MetricsTest.java create mode 100644 libs/java/client_common/src/test/java/com/yahoo/athenz/common/utils/SignUtilsTest.java create mode 100644 libs/java/client_common/src/test/resources/logback.xml create mode 100644 libs/java/server_common/README.md create mode 100644 libs/java/server_common/pom.xml create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugKerberosAuthority.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugPrincipalAuthority.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugRoleAuthority.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugUserAuthority.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/filters/DefaultMediaTypeFilter.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/filters/ETagFilter.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AthenzRequestLog.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogFactory.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogMsgBuilder.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogger.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/impl/DefaultAuditLogMsgBuilder.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/impl/DefaultAuditLogger.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/Http.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/HttpContainer.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/ResourceContext.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/ResourceException.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/RestCoreResourceConfig.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/util/ServletRequestUtil.java create mode 100644 libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/util/StringUtils.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugKerberosAuthorityTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugPrincipalAuthorityTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugRoleAuthorityTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugUserAuthorityTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/ETagFilterTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/MockHttpServletRequest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/MockHttpServletResponse.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/AthenzRequestLogTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/AuditLogMsgBuilderTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/AuditLoggerTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/TestLogger.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/TestMsgBuilder.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/HttpContainerTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/HttpTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/ResourceContextTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/ResourceExceptionTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/RestCoreResourceConfigTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/util/ServletRequestUtilTest.java create mode 100644 libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/util/StringUtilsTest.java create mode 100644 libs/java/server_common/src/test/resources/logback.xml create mode 100644 pom.xml create mode 100644 servers/zms/README.md create mode 100644 servers/zms/conf/authorized_services.json create mode 100644 servers/zms/conf/container_settings create mode 100755 servers/zms/conf/logback.xml create mode 100644 servers/zms/conf/solution_templates.json create mode 100644 servers/zms/pom.xml create mode 100644 servers/zms/schema/zms_server.mwb create mode 100644 servers/zms/schema/zms_server.sql create mode 100755 servers/zms/scripts/make_stubs.sh create mode 100755 servers/zms/scripts/zms_debug.sh create mode 100644 servers/zms/scripts/zms_start.sh create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/provider/ProviderClient.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/provider/ResourceError.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/provider/ResourceException.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/DBService.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/GetSignedDomainsResult.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceContext.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceError.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceException.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/ZMS.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSConsts.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSDaemon.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSHandler.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSImpl.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSJettyContainer.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSResources.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSServerImpl.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/config/AllowedOperation.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/config/AuthorizedService.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/config/AuthorizedServices.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/config/SolutionTemplates.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/PrivateKeyStore.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/PrivateKeyStoreFactory.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStore.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStoreFactory.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/store/AthenzDomain.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStore.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStoreConnection.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/DomainStruct.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/FileConnection.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/FileObjectStore.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/DataSourceFactory.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/JDBCConnection.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/JDBCObjectStore.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/PoolableDataSource.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/ZMSDataSource.java create mode 100644 servers/zms/src/main/java/com/yahoo/athenz/zms/utils/ZMSUtils.java create mode 100644 servers/zms/src/main/rdl/ZMS.rdl create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/provider/ProviderMockClient.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/provider/ProviderMockClientTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/DBServiceTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/MockConnectionFactory.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/MockHttpServletRequest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/MockHttpServletResponse.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/ResourceExceptionTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSDaemonTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSImplTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSJettyContainerTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSServerImplTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/config/AllowedOperationTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/config/AuthorizedServicesTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/config/SolutionTemplatesTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/pkey/PrivateKeyStoreTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStoreTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/store/file/FileConnectionTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/store/file/FileObjectStoreTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/DataSourceFactoryTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/JDBCConnectionTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/JDBCObjectStoreTest.java create mode 100644 servers/zms/src/test/java/com/yahoo/athenz/zms/utils/ZMSUtilsTest.java create mode 100644 servers/zms/src/test/resources/authorized_services.json create mode 100644 servers/zms/src/test/resources/logback.xml create mode 100644 servers/zms/src/test/resources/solution_templates.json create mode 100644 servers/zms/src/test/resources/zms_private.pem create mode 100644 servers/zms/src/test/resources/zms_private_k1.pem create mode 100644 servers/zms/src/test/resources/zms_private_k2.pem create mode 100644 servers/zms/src/test/resources/zms_public.pem create mode 100644 servers/zms/src/test/resources/zms_public_k1.pem create mode 100644 servers/zms/src/test/resources/zms_public_k2.pem create mode 100644 servers/zts/README.md create mode 100644 servers/zts/conf/aws_public.crt create mode 100644 servers/zts/conf/container_settings create mode 100755 servers/zts/conf/logback.xml create mode 100644 servers/zts/pom.xml create mode 100755 servers/zts/scripts/make_stubs.sh create mode 100755 servers/zts/scripts/zts_debug.sh create mode 100644 servers/zts/scripts/zts_start.sh create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/GetDomainSignedPolicyDataResult.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceContext.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceError.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceException.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/RsrcCtxWrapper.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ZTS.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSAuthorizer.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSBinder.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSDaemon.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSHandler.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSJettyContainer.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSResources.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/cache/DataCache.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/cache/DataCacheProvider.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/cert/CertSigner.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/cert/CertSignerFactory.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/cert/SvcCertStore.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/cert/SvcCertStoreFactory.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/cert/X509CertSignObject.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YCertSigner.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YCertSignerFactory.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YSvcCertStore.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YSvcCertStoreFactory.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/PrivateKeyStore.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/PrivateKeyStoreFactory.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStore.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStoreFactory.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStore.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStoreFactory.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/store/ChangeLogStore.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/store/ChangeLogStoreFactory.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/store/CloudStore.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/store/DataStore.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStore.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreFactory.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStore.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreFactory.java create mode 100644 servers/zts/src/main/java/com/yahoo/athenz/zts/utils/ZTSUtils.java create mode 100644 servers/zts/src/main/rdl/ZTS.rdl create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/ResourceExceptionTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/ResultObjectTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/RsrcCtxWrapperTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSBinderTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSDaemonTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSJettyContainerTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/cache/DataCacheTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/cert/MockCertSigner.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/cert/MockCertSignerFactory.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/cert/X509CertSignObjectTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/cert/impl/YCertSignerTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStoreTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStoreTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/store/CloudStoreTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/store/DataStoreTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/store/MockCloudStore.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/MockZMSFileChangeLogStore.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/MockZMSFileChangeLogStoreFactory.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreFactoryTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/MockS3ChangeLogStore.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/MockS3ChangeLogStoreFactory.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreFactoryTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/utils/ZTSUtilsTest.java create mode 100644 servers/zts/src/test/resources/athenz.conf create mode 100644 servers/zts/src/test/resources/iaas.json create mode 100644 servers/zts/src/test/resources/logback.xml create mode 100644 servers/zts/src/test/resources/private_encrypted.key create mode 100644 servers/zts/src/test/resources/test_private.v1 create mode 100644 servers/zts/src/test/resources/test_public.v1 create mode 100644 servers/zts/src/test/resources/valid.csr create mode 100644 servers/zts/src/test/resources/valid_cn_x509.cert create mode 100644 servers/zts/src/test/resources/zts_private.pem create mode 100644 servers/zts/src/test/resources/zts_public.pem create mode 100644 utils/zms_cli/README.md create mode 100644 utils/zms_cli/zms-cli.go create mode 100644 utils/zpe_policy_updater/README.md create mode 100755 utils/zpe_policy_updater/conf/logback.xml create mode 100644 utils/zpe_policy_updater/conf/utility_settings create mode 100644 utils/zpe_policy_updater/conf/zpu.conf create mode 100644 utils/zpe_policy_updater/pom.xml create mode 100755 utils/zpe_policy_updater/scripts/zpu_run.sh create mode 100755 utils/zpe_policy_updater/scripts/zpu_set_ownership.pl create mode 100644 utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdater.java create mode 100644 utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdaterConfiguration.java create mode 100644 utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/SIAClientFactory.java create mode 100644 utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/SIAClientFactoryImpl.java create mode 100644 utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/ZTSClientFactory.java create mode 100644 utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/ZTSClientFactoryImpl.java create mode 100644 utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/DebugSIAClientFactory.java create mode 100644 utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/DebugZTSClientFactory.java create mode 100644 utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/MockMetricFactory.java create mode 100644 utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdaterTest.java create mode 100644 utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/SIAClientMock.java create mode 100644 utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/SignPoliciesUtility.java create mode 100644 utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/ZTSMock.java create mode 100755 utils/zpe_policy_updater/src/test/resources/athenz.conf create mode 100644 utils/zpe_policy_updater/src/test/resources/logback.xml create mode 100644 utils/zpe_policy_updater/src/test/resources/pol_dir/.keepme create mode 100644 utils/zpe_policy_updater/src/test/resources/sports.pol create mode 100644 utils/zpe_policy_updater/src/test/resources/sys.auth.pol create mode 100644 utils/zpe_policy_updater/src/test/resources/sys.auth.pol.tampered.zms create mode 100644 utils/zpe_policy_updater/src/test/resources/sys.auth.pol.tampered.zts create mode 100644 utils/zpe_policy_updater/src/test/resources/tmp/.keepme create mode 100644 utils/zpe_policy_updater/src/test/resources/zms_private_k0.pem create mode 100644 utils/zpe_policy_updater/src/test/resources/zms_public_k0.pem create mode 100644 utils/zpe_policy_updater/src/test/resources/zpu.conf create mode 100644 utils/zpe_policy_updater/src/test/resources/zpu_empty.conf create mode 100644 utils/zpe_policy_updater/src/test/resources/zpu_private.pem create mode 100644 utils/zpe_policy_updater/src/test/resources/zpu_public.pem create mode 100644 utils/zpe_policy_updater/src/test/resources/zts_private_k0.pem create mode 100644 utils/zpe_policy_updater/src/test/resources/zts_private_k1.pem create mode 100644 utils/zpe_policy_updater/src/test/resources/zts_public_k0.pem create mode 100644 utils/zpe_policy_updater/src/test/resources/zts_public_k1.pem diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..52136798542 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*~ +**/.classpath +**/.project +**/.settings +**/target/ +**/dependency-reduced-pom.xml +servers/zms/zms_logs/ +servers/zms/zms_root/ +servers/zts/zts_logs/ +servers/zts/zts_store/ +clients/go/zms/pkg/ +clients/go/zms/src/ +clients/go/zts/pkg/ +clients/go/zts/src/ +clients/java/sia/src/test/resources/svc.ntoken +clients/java/zpe/src/test/resources/upd_pol_dir/ +ui/build/ +ui/node_modules/ +utils/zms_cli/zms-cli +utils/zpe_policy_updater/tmp_metrics/ +utils/zpe_policy_updater/src/test/resources/sys.auth.new.pol diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 00000000000..6f46a6d0c03 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ + + +Athenz is a set of services and libraries supporting role-based +authorization (RBAC) for provisioning and configuration (centralized +authorization) use cases as well as serving/runtime (decentralized +authorization) use cases. Athenz authorization system utilizes two +types of tokens: Principal Tokens (N-Tokens) and RoleTokens (Z-Tokens). +The name "Athenz" is derived from "Auth" and the 'N' and 'Z' tokens. + +## Main features +---------------- + +Athenz provides both the functionality of a centralized system +and a certificate and IP-based distributed system to handle +on-box enforcement. + +You get the following advantages using Athenz: + +- **Service-based security profile:** Security definitions that + automatically trickle down to hosts within the service. +- **Dynamic provisioning:** Scale fast or move workloads around + without manual intervention (IP-less configuration). +- **Single source of truth:** Consolidated service profile serving + various downstream security implementations, including support for + non-user entities. +- **Self-Service:** Real-time configuration and enforcement of + resource-based access control (dynamic manageability). + +More importantly, we want engineers to use Athenz and **not** build +their own role-based access control systems that have no central store +and often rely on network ACLs and manual updating. + +## Documentation +---------------- + +* Getting Started + * [Development Enviornment](docs/dev_environment.md) + * [Setup ZMS](docs/setup_zms.md) + * [Setup ZTS](docs/setup_zts.md) +* Architecture + * [Data Model](docs/data_model.md) + * [System View](docs/system_view.md) + * [Authorization Flow](docs/auth_flow.md) +* Developer Guide + * [Centralized Access Control](docs/dev_centralized_access.md) + * [Decentralized Access Control](docs/dev_decentralized_access.md) + * [System Properties](docs/system_properties.md) +* User Guide + * [ZMS Client Utility](docs/zms_client.md) + * [Registering ZMS Service Identity](docs/reg_service_guide.md) + +## Contact +---------- + +* [Athenz-Dev](https://groups.google.com/d/forum/athenz-dev) for + development discussions +* [Athenz-Users](https://groups.google.com/d/forum/athenz-users) for + users questions + +## License +---------- + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/assembly/components/zms.xml b/assembly/components/zms.xml new file mode 100644 index 00000000000..ac0754b9566 --- /dev/null +++ b/assembly/components/zms.xml @@ -0,0 +1,87 @@ + + + + bin + + tar.gz + + true + + + ${basedir}/../servers/zms/conf + conf/zms_server + + + ${basedir}/../servers/zms/scripts + bin + 755 + + zms_start.sh + + + + ${basedir}/../servers/zms/target/dependency + lib/jars + + + . + logs/zms_server + + */** + + + + . + var/zms_server/keys + + */** + + + + . + var/zms_server/certs + + */** + + + + + + ${basedir}/../servers/zms/README.md + . + 644 + + + ${basedir}/../LICENSE + . + 644 + + + + + + com.yahoo.athenz:zms_server + + lib/jars + false + runtime + false + + + diff --git a/assembly/components/zpu.xml b/assembly/components/zpu.xml new file mode 100644 index 00000000000..62c4c8dc6f5 --- /dev/null +++ b/assembly/components/zpu.xml @@ -0,0 +1,90 @@ + + + + bin + + tar.gz + + true + + + ${basedir}/../utils/zpe_policy_updater/conf + conf/zpe_policy_updater + + + ${basedir}/../utils/zpe_policy_updater/scripts + bin + 755 + + zpu_run.sh + + + + . + logs/zpe_policy_updater + + */** + + + + . + var/zpe + + */** + + + + . + tmp/zpe + + */** + + + + . + var/zpe_policy_updater/certs + + */** + + + + + + ${basedir}/../utils/zpe_policy_updater/README.md + . + 644 + + + ${basedir}/../LICENSE + . + 644 + + + + + + com.yahoo.athenz:zpe_policy_updater + + lib/jars + false + runtime + false + + + diff --git a/assembly/components/zts.xml b/assembly/components/zts.xml new file mode 100644 index 00000000000..e65d5e54cb5 --- /dev/null +++ b/assembly/components/zts.xml @@ -0,0 +1,87 @@ + + + + bin + + tar.gz + + true + + + ${basedir}/../servers/zts/conf + conf/zts_server + + + ${basedir}/../servers/zts/scripts + bin + 755 + + zts_start.sh + + + + ${basedir}/../servers/zts/target/dependency + lib/jars + + + . + logs/zts_server + + */** + + + + . + var/zts_server/keys + + */** + + + + . + var/zts_server/certs + + */** + + + + + + ${basedir}/../servers/zts/README.md + . + 644 + + + ${basedir}/../LICENSE + . + 644 + + + + + + com.yahoo.athenz:zts_server + + lib/jars + false + runtime + false + + + diff --git a/assembly/pom.xml b/assembly/pom.xml new file mode 100644 index 00000000000..b92b2c522f3 --- /dev/null +++ b/assembly/pom.xml @@ -0,0 +1,99 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../pom.xml + + + assembly + assembly + pom + + + + com.yahoo.athenz + zms_server + ${project.parent.version} + + + com.yahoo.athenz + zts_server + ${project.parent.version} + + + com.yahoo.athenz + zpe_policy_updater + ${project.parent.version} + + + + + + + maven-assembly-plugin + 2.6 + + + package-zms-assembly + package + + single + + + posix + athenz-zms-${project.version} + + components/zms.xml + + + + + package-zts-assembly + package + + single + + + posix + athenz-zts-${project.version} + + components/zts.xml + + + + + package-zpu-assembly + package + + single + + + posix + athenz-zpu-${project.version} + + components/zpu.xml + + + + + + + + diff --git a/clients/go/zms/Makefile b/clients/go/zms/Makefile new file mode 100644 index 00000000000..6e6e2d4de6d --- /dev/null +++ b/clients/go/zms/Makefile @@ -0,0 +1,20 @@ +RDL_FILE=../../../core/zms/src/main/rdl/ZMS.rdl +RDL_LIB=github.com/ardielle/ardielle-go/rdl + +export GOPATH=$(PWD) + +all: model.go client.go build + +build: src/$(RDL_LIB) + +src/$(RDL_LIB): + go get $(RDL_LIB) + +model.go: $(RDL_FILE) + rdl -ps generate -t -o $@ go-model $(RDL_FILE) + +client.go: $(RDL_FILE) + rdl -ps generate -t -o $@ go-client $(RDL_FILE) + +clean:: + rm -rf model.go client.go zms_schema.go *~ ./src diff --git a/clients/go/zms/README.md b/clients/go/zms/README.md new file mode 100644 index 00000000000..5aad5912dd1 --- /dev/null +++ b/clients/go/zms/README.md @@ -0,0 +1,54 @@ +# zms-go-client + +A Go client library to talk to Athenz ZMS. + +The model.go and client.go files are generated from zms_core, and checked in so users of this library need not know that. + +Additionally, an implementation of rdl.Authorizer and rdl.Authenticator are provided that use this library to delegate that functionality to Athenz ZMS: + +Release Notes: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Version 1.0 (2016-09-06) + - Initial opensource release + +## Usage + +To get it into your workspace: + + go get github.com/yahoo/athenz/clients/go/zms + +Then in your Go code: + + import ( + zms "github.com/yahoo/athenz/clients/go/zms" + ) + func main() { + var principal rdl.Principal /* init this from an actual user credential */ + ... + client := zms.NewClient() + client.AddCredentials(principal.GetHTTPHeaderName(), principal.GetCredentials()) + dmn, err := client.GetDomain("athenz") // + ... + } + +To use the ZMSAuthorizer from your RDL-generated server: + + import ( + zms "github.com/yahoo/athenz/clients/go/zms" + ) + ... + endpoint := "localhost:4080" + domain := "your.server.domain" + + zmsURL := "http://localhost:10080/zms/v1" //set this to "" for debug mode + authn := zms.Authenticator(zmsURL) + authz := zms.Authorizer(domain, zmsURL) + + handler := contacts.Init(impl, url, authz, authn) + http.ListenAndServe(endpoint, handler) + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/clients/go/zms/auth.go b/clients/go/zms/auth.go new file mode 100644 index 00000000000..49d640185f7 --- /dev/null +++ b/clients/go/zms/auth.go @@ -0,0 +1,112 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zms + +import ( + "fmt" + "github.com/ardielle/ardielle-go/rdl" + "log" + "strings" +) + +// +// Authenticator - an unoptimized authenticator that delegates to ZMS. +// The advantage is that there is no local state or config other than the +// url of ZMS (we don't need ZMS's public key to be local). +// +func Authenticator(url string) rdl.Authenticator { + return &zmsAuthenticator{url} +} + +type zmsAuthenticator struct { + url string //i.e. "http://localhost:10080/zms/v1" +} + +func (ath zmsAuthenticator) HTTPHeader() string { + return "Athenz-Principal-Auth" +} + +func (ath zmsAuthenticator) Authenticate(nToken string) rdl.Principal { + attrs := make(map[string]string) + for _, attr := range strings.Split(nToken, ";") { + kv := strings.Split(attr, "=") + attrs[kv[0]] = kv[1] + } + if name, ok := attrs["n"]; ok { + if domain, ok := attrs["d"]; ok { + //do not verify the token, because we will just pass it off in the authorizer, where ZMS will do that. + log.Printf("[Authenticate %s.%s]\n", domain, name) + return zmsPrincipal{domain, name, nToken, ath.HTTPHeader()} + } + } + return nil +} + +//zmsPrincipal implements rdl.Principal the interface +type zmsPrincipal struct { + domain string + name string + creds string + header string +} + +func (p zmsPrincipal) GetDomain() string { + return p.domain +} + +func (p zmsPrincipal) GetName() string { + return p.name +} + +func (p zmsPrincipal) GetYRN() string { + return p.domain + "." + p.name +} + +func (p zmsPrincipal) GetCredentials() string { + return p.creds +} + +func (p zmsPrincipal) GetHTTPHeaderName() string { + return p.header +} + +// +// Authorizer returns an authorizer that calls zms. If the url is set to +// "", then the access is logged, but always succeeds (for debug purposes) +// +func Authorizer(domain string, url string) rdl.Authorizer { + return &zmsAuthorizer{domain: domain, url: url} +} + +type zmsAuthorizer struct { + domain string + url string +} + +func (auth zmsAuthorizer) Authorize(action string, resource string, principal rdl.Principal) (bool, error) { + //this should be done before getting here! + if strings.Index(resource, ":") < 0 { + //the resource is relative to the service's domain + resource = auth.domain + ":" + resource + } + if auth.url == "" { + log.Printf("[DEBUG Authorize %s on %s for %s]\n", action, resource, principal.GetYRN()) + return true, nil + } else { + if principal.GetHTTPHeaderName() != "Athenz-Principal-Auth" { + return false, fmt.Errorf("Authorizer using" + principal.GetHTTPHeaderName() + " not supported") + } + zmsClient := NewClient(auth.url, nil) + zmsClient.AddCredentials(principal.GetHTTPHeaderName(), principal.GetCredentials()) + check, err := zmsClient.GetAccess(ActionName(action), YRN(resource), "", "") + if err != nil { + return false, err + } + log.Printf("[Authorize %s on %s for %s: %v]\n", action, resource, principal.GetYRN(), check.Granted) + if check.Granted { + return true, nil + } + return false, nil + } +} diff --git a/clients/go/zms/client.go b/clients/go/zms/client.go new file mode 100644 index 00000000000..5b7c211eab0 --- /dev/null +++ b/clients/go/zms/client.go @@ -0,0 +1,2377 @@ +// +// This file generated by rdl 1.4.8 +// + +package zms + +import ( + "bytes" + "encoding/json" + "fmt" + rdl "github.com/ardielle/ardielle-go/rdl" + "io" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" + "time" +) + +var _ = json.Marshal +var _ = fmt.Printf +var _ = rdl.BaseTypeAny +var _ = ioutil.NopCloser + +type ZMSClient struct { + URL string + Transport http.RoundTripper + CredsHeader *string + CredsToken *string + Timeout time.Duration +} + +// NewClient creates and returns a new HTTP client object for the ZMS service +func NewClient(url string, transport http.RoundTripper) ZMSClient { + return ZMSClient{url, transport, nil, nil, 0} +} + +// AddCredentials adds the credentials to the client for subsequent requests. +func (client *ZMSClient) AddCredentials(header string, token string) { + client.CredsHeader = &header + client.CredsToken = &token +} + +func (client ZMSClient) getClient() *http.Client { + var c *http.Client + if client.Transport != nil { + c = &http.Client{Transport: client.Transport} + } else { + c = &http.Client{} + } + if client.Timeout > 0 { + c.Timeout = client.Timeout + } + return c +} + +func (client ZMSClient) addAuthHeader(req *http.Request) { + if client.CredsHeader != nil && client.CredsToken != nil { + if strings.HasPrefix(*client.CredsHeader, "Cookie.") { + req.Header.Add("Cookie", (*client.CredsHeader)[7:]+"="+*client.CredsToken) + } else { + req.Header.Add(*client.CredsHeader, *client.CredsToken) + } + } +} + +func (client ZMSClient) httpGet(url string, headers map[string]string) (*http.Response, error) { + hclient := client.getClient() + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func (client ZMSClient) httpDelete(url string, headers map[string]string) (*http.Response, error) { + hclient := client.getClient() + req, err := http.NewRequest("DELETE", url, nil) + if err != nil { + return nil, err + } + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func (client ZMSClient) httpPut(url string, headers map[string]string, body []byte) (*http.Response, error) { + contentReader := bytes.NewReader(body) + hclient := client.getClient() + req, err := http.NewRequest("PUT", url, contentReader) + if err != nil { + return nil, err + } + req.Header.Add("Content-type", "application/json") + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func (client ZMSClient) httpPost(url string, headers map[string]string, body []byte) (*http.Response, error) { + contentReader := bytes.NewReader(body) + hclient := client.getClient() + req, err := http.NewRequest("POST", url, contentReader) + if err != nil { + return nil, err + } + req.Header.Add("Content-type", "application/json") + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func (client ZMSClient) httpPatch(url string, headers map[string]string, body []byte) (*http.Response, error) { + contentReader := bytes.NewReader(body) + hclient := client.getClient() + req, err := http.NewRequest("PATCH", url, contentReader) + if err != nil { + return nil, err + } + req.Header.Add("Content-type", "application/json") + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func (client ZMSClient) httpOptions(url string, headers map[string]string, body []byte) (*http.Response, error) { + var contentReader io.Reader = nil + if body != nil { + contentReader = bytes.NewReader(body) + } + hclient := client.getClient() + req, err := http.NewRequest("OPTIONS", url, contentReader) + if err != nil { + return nil, err + } + if contentReader != nil { + req.Header.Add("Content-type", "application/json") + } + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func encodeStringParam(name string, val string, def string) string { + if val == def { + return "" + } + return "&" + name + "=" + url.QueryEscape(val) +} +func encodeBoolParam(name string, b bool, def bool) string { + if b == def { + return "" + } + return fmt.Sprintf("&%s=%v", name, b) +} +func encodeInt8Param(name string, i int8, def int8) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.Itoa(int(i)) +} +func encodeInt16Param(name string, i int16, def int16) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.Itoa(int(i)) +} +func encodeInt32Param(name string, i int32, def int32) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.Itoa(int(i)) +} +func encodeInt64Param(name string, i int64, def int64) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.FormatInt(i, 10) +} +func encodeFloat32Param(name string, i float32, def float32) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.FormatFloat(float64(i), 'g', -1, 32) +} +func encodeFloat64Param(name string, i float64, def float64) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.FormatFloat(i, 'g', -1, 64) +} +func encodeOptionalEnumParam(name string, e interface{}) string { + if e == nil { + return "\"\"" + } + return fmt.Sprintf("&%s=%v", name, e) +} +func encodeOptionalBoolParam(name string, b *bool) string { + if b == nil { + return "" + } + return fmt.Sprintf("&%s=%v", name, *b) +} +func encodeOptionalInt32Param(name string, i *int32) string { + if i == nil { + return "" + } + return "&" + name + "=" + strconv.Itoa(int(*i)) +} +func encodeOptionalInt64Param(name string, i *int64) string { + if i == nil { + return "" + } + return "&" + name + "=" + strconv.Itoa(int(*i)) +} +func encodeParams(objs ...string) string { + s := strings.Join(objs, "") + if s == "" { + return s + } + return "?" + s[1:] +} + +func (client ZMSClient) GetDomain(domain DomainName) (*Domain, error) { + var data *Domain + url := client.URL + "/domain/" + fmt.Sprint(domain) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetDomainList(limit *int32, skip string, prefix string, depth *int32, account string, productId *int32, roleMember ResourceName, roleName ResourceName, modifiedSince string) (*DomainList, error) { + var data *DomainList + headers := map[string]string{ + "If-Modified-Since": modifiedSince, + } + url := client.URL + "/domain" + encodeParams(encodeOptionalInt32Param("limit", limit), encodeStringParam("skip", string(skip), ""), encodeStringParam("prefix", string(prefix), ""), encodeOptionalInt32Param("depth", depth), encodeStringParam("account", string(account), ""), encodeOptionalInt32Param("ypmid", productId), encodeStringParam("member", string(roleMember), ""), encodeStringParam("role", string(roleName), "")) + resp, err := client.httpGet(url, headers) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) PostTopLevelDomain(auditRef string, detail *TopLevelDomain) (*Domain, error) { + var data *Domain + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain" + contentBytes, err := json.Marshal(detail) + if err != nil { + return data, err + } + resp, err := client.httpPost(url, headers, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) PostSubDomain(parent DomainName, auditRef string, detail *SubDomain) (*Domain, error) { + var data *Domain + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/subdomain/" + fmt.Sprint(parent) + contentBytes, err := json.Marshal(detail) + if err != nil { + return data, err + } + resp, err := client.httpPost(url, headers, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) PostUserDomain(name SimpleName, auditRef string, detail *UserDomain) (*Domain, error) { + var data *Domain + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/userdomain/" + fmt.Sprint(name) + contentBytes, err := json.Marshal(detail) + if err != nil { + return data, err + } + resp, err := client.httpPost(url, headers, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) DeleteTopLevelDomain(name DomainName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(name) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) DeleteSubDomain(parent DomainName, name DomainName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/subdomain/" + fmt.Sprint(parent) + "/" + fmt.Sprint(name) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) DeleteUserDomain(name SimpleName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/userdomain/" + fmt.Sprint(name) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) PutDomainMeta(name DomainName, auditRef string, detail *DomainMeta) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(name) + "/meta" + contentBytes, err := json.Marshal(detail) + if err != nil { + return err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) PutDomainTemplate(name DomainName, auditRef string, template *DomainTemplate) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(name) + "/template" + contentBytes, err := json.Marshal(template) + if err != nil { + return err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) GetDomainTemplateList(name DomainName) (*DomainTemplateList, error) { + var data *DomainTemplateList + url := client.URL + "/domain/" + fmt.Sprint(name) + "/template" + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) DeleteDomainTemplate(name DomainName, template SimpleName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(name) + "/template/" + fmt.Sprint(template) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) GetDomainDataCheck(domainName DomainName) (*DomainDataCheck, error) { + var data *DomainDataCheck + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/check" + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) PutEntity(domainName DomainName, entityName EntityName, auditRef string, entity *Entity) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/entity/" + fmt.Sprint(entityName) + contentBytes, err := json.Marshal(entity) + if err != nil { + return err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) GetEntity(domainName DomainName, entityName EntityName) (*Entity, error) { + var data *Entity + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/entity/" + fmt.Sprint(entityName) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) DeleteEntity(domainName DomainName, entityName EntityName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/entity/" + fmt.Sprint(entityName) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) GetEntityList(domainName DomainName) (*EntityList, error) { + var data *EntityList + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/entity" + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetRoleList(domainName DomainName, limit *int32, skip string) (*RoleList, error) { + var data *RoleList + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/role" + encodeParams(encodeOptionalInt32Param("limit", limit), encodeStringParam("skip", string(skip), "")) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetRoles(domainName DomainName, members *bool) (*Roles, error) { + var data *Roles + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/roles" + encodeParams(encodeOptionalBoolParam("members", members)) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetRole(domainName DomainName, roleName EntityName, auditLog *bool, expand *bool) (*Role, error) { + var data *Role + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/role/" + fmt.Sprint(roleName) + encodeParams(encodeOptionalBoolParam("auditLog", auditLog), encodeOptionalBoolParam("expand", expand)) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) PutRole(domainName DomainName, roleName EntityName, auditRef string, role *Role) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/role/" + fmt.Sprint(roleName) + contentBytes, err := json.Marshal(role) + if err != nil { + return err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) DeleteRole(domainName DomainName, roleName EntityName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/role/" + fmt.Sprint(roleName) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) GetMembership(domainName DomainName, roleName EntityName, memberName ResourceName) (*Membership, error) { + var data *Membership + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/role/" + fmt.Sprint(roleName) + "/member/" + fmt.Sprint(memberName) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) PutMembership(domainName DomainName, roleName EntityName, memberName ResourceName, auditRef string, membership *Membership) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/role/" + fmt.Sprint(roleName) + "/member/" + fmt.Sprint(memberName) + contentBytes, err := json.Marshal(membership) + if err != nil { + return err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) DeleteMembership(domainName DomainName, roleName EntityName, memberName ResourceName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/role/" + fmt.Sprint(roleName) + "/member/" + fmt.Sprint(memberName) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) PutDefaultAdmins(domainName DomainName, auditRef string, defaultAdmins *DefaultAdmins) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/admins" + contentBytes, err := json.Marshal(defaultAdmins) + if err != nil { + return err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) GetPolicyList(domainName DomainName, limit *int32, skip string) (*PolicyList, error) { + var data *PolicyList + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/policy" + encodeParams(encodeOptionalInt32Param("limit", limit), encodeStringParam("skip", string(skip), "")) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetPolicies(domainName DomainName, assertions *bool) (*Policies, error) { + var data *Policies + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/policies" + encodeParams(encodeOptionalBoolParam("assertions", assertions)) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetPolicy(domainName DomainName, policyName EntityName) (*Policy, error) { + var data *Policy + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/policy/" + fmt.Sprint(policyName) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) PutPolicy(domainName DomainName, policyName EntityName, auditRef string, policy *Policy) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/policy/" + fmt.Sprint(policyName) + contentBytes, err := json.Marshal(policy) + if err != nil { + return err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) DeletePolicy(domainName DomainName, policyName EntityName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/policy/" + fmt.Sprint(policyName) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) GetAssertion(domainName DomainName, policyName EntityName, assertionId int64) (*Assertion, error) { + var data *Assertion + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/policy/" + fmt.Sprint(policyName) + "/assertion/" + fmt.Sprint(assertionId) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) PutAssertion(domainName DomainName, policyName EntityName, auditRef string, assertion *Assertion) (*Assertion, error) { + var data *Assertion + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/policy/" + fmt.Sprint(policyName) + "/assertion" + contentBytes, err := json.Marshal(assertion) + if err != nil { + return data, err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200, 201: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) DeleteAssertion(domainName DomainName, policyName EntityName, assertionId int64, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/policy/" + fmt.Sprint(policyName) + "/assertion/" + fmt.Sprint(assertionId) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) PutServiceIdentity(domain DomainName, service SimpleName, auditRef string, detail *ServiceIdentity) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + contentBytes, err := json.Marshal(detail) + if err != nil { + return err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) GetServiceIdentity(domain DomainName, service SimpleName) (*ServiceIdentity, error) { + var data *ServiceIdentity + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) DeleteServiceIdentity(domain DomainName, service SimpleName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) GetServiceIdentities(domainName DomainName, publickeys *bool, hosts *bool) (*ServiceIdentities, error) { + var data *ServiceIdentities + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/services" + encodeParams(encodeOptionalBoolParam("publickeys", publickeys), encodeOptionalBoolParam("hosts", hosts)) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetServiceIdentityList(domainName DomainName, limit *int32, skip string) (*ServiceIdentityList, error) { + var data *ServiceIdentityList + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/service" + encodeParams(encodeOptionalInt32Param("limit", limit), encodeStringParam("skip", string(skip), "")) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetPublicKeyEntry(domain DomainName, service SimpleName, id string) (*PublicKeyEntry, error) { + var data *PublicKeyEntry + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + "/publickey/" + id + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) PutPublicKeyEntry(domain DomainName, service SimpleName, id string, auditRef string, publicKeyEntry *PublicKeyEntry) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + "/publickey/" + id + contentBytes, err := json.Marshal(publicKeyEntry) + if err != nil { + return err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) DeletePublicKeyEntry(domain DomainName, service SimpleName, id string, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + "/publickey/" + id + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) PutTenancy(domain DomainName, service ServiceName, auditRef string, detail *Tenancy) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/tenancy/" + fmt.Sprint(service) + contentBytes, err := json.Marshal(detail) + if err != nil { + return err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) GetTenancy(domain DomainName, service ServiceName) (*Tenancy, error) { + var data *Tenancy + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/tenancy/" + fmt.Sprint(service) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) DeleteTenancy(domain DomainName, service ServiceName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/tenancy/" + fmt.Sprint(service) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) PutTenancyResourceGroup(domain DomainName, service ServiceName, resourceGroup EntityName, auditRef string, detail *TenancyResourceGroup) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/tenancy/" + fmt.Sprint(service) + "/resourceGroup/" + fmt.Sprint(resourceGroup) + contentBytes, err := json.Marshal(detail) + if err != nil { + return err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) DeleteTenancyResourceGroup(domain DomainName, service ServiceName, resourceGroup EntityName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/tenancy/" + fmt.Sprint(service) + "/resourceGroup/" + fmt.Sprint(resourceGroup) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) PutTenantRoles(domain DomainName, service SimpleName, tenantDomain DomainName, auditRef string, detail *TenantRoles) (*TenantRoles, error) { + var data *TenantRoles + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + "/tenant/" + fmt.Sprint(tenantDomain) + contentBytes, err := json.Marshal(detail) + if err != nil { + return data, err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200, 201: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetTenantRoles(domain DomainName, service SimpleName, tenantDomain DomainName) (*TenantRoles, error) { + var data *TenantRoles + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + "/tenant/" + fmt.Sprint(tenantDomain) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) DeleteTenantRoles(domain DomainName, service SimpleName, tenantDomain DomainName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + "/tenant/" + fmt.Sprint(tenantDomain) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) PutTenantResourceGroupRoles(domain DomainName, service SimpleName, tenantDomain DomainName, resourceGroup EntityName, auditRef string, detail *TenantResourceGroupRoles) (*TenantResourceGroupRoles, error) { + var data *TenantResourceGroupRoles + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + "/tenant/" + fmt.Sprint(tenantDomain) + "/resourceGroup/" + fmt.Sprint(resourceGroup) + contentBytes, err := json.Marshal(detail) + if err != nil { + return data, err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200, 201: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetTenantResourceGroupRoles(domain DomainName, service SimpleName, tenantDomain DomainName, resourceGroup EntityName) (*TenantResourceGroupRoles, error) { + var data *TenantResourceGroupRoles + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + "/tenant/" + fmt.Sprint(tenantDomain) + "/resourceGroup/" + fmt.Sprint(resourceGroup) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) DeleteTenantResourceGroupRoles(domain DomainName, service SimpleName, tenantDomain DomainName, resourceGroup EntityName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(domain) + "/service/" + fmt.Sprint(service) + "/tenant/" + fmt.Sprint(tenantDomain) + "/resourceGroup/" + fmt.Sprint(resourceGroup) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) PutProviderResourceGroupRoles(tenantDomain DomainName, provDomain DomainName, provService SimpleName, resourceGroup EntityName, auditRef string, detail *ProviderResourceGroupRoles) (*ProviderResourceGroupRoles, error) { + var data *ProviderResourceGroupRoles + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(tenantDomain) + "/provDomain/" + fmt.Sprint(provDomain) + "/provService/" + fmt.Sprint(provService) + "/resourceGroup/" + fmt.Sprint(resourceGroup) + contentBytes, err := json.Marshal(detail) + if err != nil { + return data, err + } + resp, err := client.httpPut(url, headers, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200, 201: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetProviderResourceGroupRoles(tenantDomain DomainName, provDomain DomainName, provService SimpleName, resourceGroup EntityName) (*ProviderResourceGroupRoles, error) { + var data *ProviderResourceGroupRoles + url := client.URL + "/domain/" + fmt.Sprint(tenantDomain) + "/provDomain/" + fmt.Sprint(provDomain) + "/provService/" + fmt.Sprint(provService) + "/resourceGroup/" + fmt.Sprint(resourceGroup) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) DeleteProviderResourceGroupRoles(tenantDomain DomainName, provDomain DomainName, provService SimpleName, resourceGroup EntityName, auditRef string) error { + headers := map[string]string{ + "Y-Audit-Ref": auditRef, + } + url := client.URL + "/domain/" + fmt.Sprint(tenantDomain) + "/provDomain/" + fmt.Sprint(provDomain) + "/provService/" + fmt.Sprint(provService) + "/resourceGroup/" + fmt.Sprint(resourceGroup) + resp, err := client.httpDelete(url, headers) + if err != nil { + return err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + switch resp.StatusCode { + case 204: + return nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return errobj + } +} + +func (client ZMSClient) GetAccess(action ActionName, resource YRN, domain DomainName, checkPrincipal EntityName) (*Access, error) { + var data *Access + url := client.URL + "/access/" + fmt.Sprint(action) + "/" + fmt.Sprint(resource) + encodeParams(encodeStringParam("domain", string(domain), ""), encodeStringParam("principal", string(checkPrincipal), "")) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetAccessExt(action ActionName, resource string, domain DomainName, checkPrincipal EntityName) (*Access, error) { + var data *Access + url := client.URL + "/access/" + fmt.Sprint(action) + encodeParams(encodeStringParam("resource", string(resource), ""), encodeStringParam("domain", string(domain), ""), encodeStringParam("principal", string(checkPrincipal), "")) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetResourceAccessList(principal EntityName, action ActionName) (*ResourceAccessList, error) { + var data *ResourceAccessList + url := client.URL + "/resource" + encodeParams(encodeStringParam("principal", string(principal), ""), encodeStringParam("action", string(action), "")) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetSignedDomains(domain DomainName, metaOnly string, matchingTag string) (*SignedDomains, string, error) { + var data *SignedDomains + headers := map[string]string{ + "If-None-Match": matchingTag, + } + url := client.URL + "/sys/modified_domains" + encodeParams(encodeStringParam("domain", string(domain), ""), encodeStringParam("metaonly", string(metaOnly), "")) + resp, err := client.httpGet(url, headers) + if err != nil { + return nil, "", err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, "", err + } + switch resp.StatusCode { + case 200, 304: + if 304 != resp.StatusCode { + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return nil, "", err + } + } + tag := resp.Header.Get(rdl.FoldHttpHeaderName("ETag")) + return data, tag, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return nil, "", errobj + } +} + +func (client ZMSClient) GetUserToken(userName SimpleName, serviceNames string) (*UserToken, error) { + var data *UserToken + url := client.URL + "/user/" + fmt.Sprint(userName) + "/token" + encodeParams(encodeStringParam("services", string(serviceNames), "")) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) OptionsUserToken(userName SimpleName, serviceNames string) (*UserToken, error) { + var data *UserToken + url := client.URL + "/user/" + fmt.Sprint(userName) + "/token" + encodeParams(encodeStringParam("services", string(serviceNames), "")) + resp, err := client.httpOptions(url, nil, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetServicePrincipal() (*ServicePrincipal, error) { + var data *ServicePrincipal + url := client.URL + "/principal" + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetServerTemplateList() (*ServerTemplateList, error) { + var data *ServerTemplateList + url := client.URL + "/template" + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZMSClient) GetTemplate(template SimpleName) (*Template, error) { + var data *Template + url := client.URL + "/template/" + fmt.Sprint(template) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} diff --git a/clients/go/zms/model.go b/clients/go/zms/model.go new file mode 100644 index 00000000000..d6c96612a8b --- /dev/null +++ b/clients/go/zms/model.go @@ -0,0 +1,3827 @@ +// +// This file generated by rdl 1.4.8 +// + +package zms + +import ( + "encoding/json" + "fmt" + rdl "github.com/ardielle/ardielle-go/rdl" +) + +var _ = rdl.Version +var _ = json.Marshal +var _ = fmt.Printf + +// +// SimpleName - Copyright 2016 Yahoo Inc. Licensed under the terms of the +// Apache version 2.0 license. See LICENSE file for terms. Common name types +// used by several API definitions A simple identifier, an element of compound +// name. +// +type SimpleName string + +// +// CompoundName - A compound name. Most names in this API are compound names. +// +type CompoundName string + +// +// DomainName - A domain name is the general qualifier prefix, as its +// uniqueness is managed. +// +type DomainName string + +// +// EntityName - An entity name is a short form of a resource name, including +// only the domain and entity. +// +type EntityName string + +// +// ServiceName - A service name will generally be a unique subdomain. +// +type ServiceName string + +// +// LocationName - A location name is not yet defined, but will be a dotted name +// like everything else. +// +type LocationName string + +// +// ActionName - An action (operation) name. +// +type ActionName string + +// +// ResourceName - A shorthand for a YRN with no service or location. The 'tail' +// of a YRN, just the domain:entity. Note that the EntityName part is optional, +// that is, a domain name followed by a colon is valid resource name. +// +type ResourceName string + +// +// YRN - A full Yahoo Resource name (YRN). +// +type YRN string + +// +// YBase64 - The Y-specific URL-safe Base64 variant. +// +type YBase64 string + +// +// YEncoded - YEncoded includes ybase64 chars, as well as = and %. This can +// represent a user cookie and URL-encoded values. +// +type YEncoded string + +// +// AuthorityName - Used as the prefix in a signed assertion. This uniquely +// identifies a signing authority. i.e. "user" +// +type AuthorityName string + +// +// SignedToken - A signed assertion if identity. i.e. the user cookie value. +// This token will only make sense to the authority that generated it, so it is +// beneficial to have something in the value that is cheaply recognized to +// quickly reject if it belongs to another authority. In addition to the +// YEncoded set our token includes ; to separate components and , to separate +// roles and : for IPv6 addresses +// +type SignedToken string + +// +// Domain - A domain is an independent partition of users, roles, and +// resources. Its name represents the definition of a namespace; the only way a +// new namespace can be created, from the top, is by creating Domains. +// Administration of a domain is governed by the parent domain (using +// reverse-DNS namespaces). The top level domains are governed by the special +// "sys.auth" domain. +// +type Domain struct { + + // + // the common name to be referred to, the symbolic id. It is immutable + // + Name DomainName `json:"name"` + + // + // the last modification timestamp of any object or attribute in this domain + // + Modified *rdl.Timestamp `json:"modified,omitempty" rdl:"optional"` + + // + // unique identifier of the domain. generated on create, never reused + // + Id *rdl.UUID `json:"id,omitempty" rdl:"optional"` + + // + // description of the domain + // + Description string `json:"description,omitempty" rdl:"optional"` + + // + // a reference to an Organization + // + Org ResourceName `json:"org,omitempty" rdl:"optional"` + + // + // Future use only, currently not used + // + Enabled *bool `json:"enabled,omitempty" rdl:"optional"` + + // + // Flag indicates whether or not domain modifications should be logged for + // SOX+Auditing. If true, the auditRef parameter must be supplied(not empty) for + // any API defining it. + // + AuditEnabled *bool `json:"auditEnabled,omitempty" rdl:"optional"` + + // + // associated cloud (i.e. aws) account id + // + Account string `json:"account,omitempty" rdl:"optional"` + + // + // associated product id + // + YpmId *int32 `json:"ypmId,omitempty" rdl:"optional"` +} + +// +// NewDomain - creates an initialized Domain instance, returns a pointer to it +// +func NewDomain(init ...*Domain) *Domain { + var o *Domain + if len(init) == 1 { + o = init[0] + } else { + o = new(Domain) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *Domain) Init() *Domain { + if pTypeDef.Enabled == nil { + d := true + pTypeDef.Enabled = &d + } + if pTypeDef.AuditEnabled == nil { + d := false + pTypeDef.AuditEnabled = &d + } + return pTypeDef +} + +type rawDomain Domain + +// +// UnmarshalJSON is defined for proper JSON decoding of a Domain +// +func (pTypeDef *Domain) UnmarshalJSON(b []byte) error { + var r rawDomain + err := json.Unmarshal(b, &r) + if err == nil { + o := Domain(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Domain) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("Domain.name is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("Domain.name does not contain a valid DomainName (%v)", val.Error) + } + } + return nil +} + +// +// RoleList - The representation for an enumeration of roles in the namespace, +// with pagination. +// +type RoleList struct { + + // + // list of role names + // + Names []EntityName `json:"names"` + + // + // if the response is a paginated list, this attribute specifies the value to + // be used in the next role list request as the value for the skip query + // parameter. + // + Next string `json:"next,omitempty" rdl:"optional"` +} + +// +// NewRoleList - creates an initialized RoleList instance, returns a pointer to it +// +func NewRoleList(init ...*RoleList) *RoleList { + var o *RoleList + if len(init) == 1 { + o = init[0] + } else { + o = new(RoleList) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *RoleList) Init() *RoleList { + if pTypeDef.Names == nil { + pTypeDef.Names = make([]EntityName, 0) + } + return pTypeDef +} + +type rawRoleList RoleList + +// +// UnmarshalJSON is defined for proper JSON decoding of a RoleList +// +func (pTypeDef *RoleList) UnmarshalJSON(b []byte) error { + var r rawRoleList + err := json.Unmarshal(b, &r) + if err == nil { + o := RoleList(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *RoleList) Validate() error { + if pTypeDef.Names == nil { + return fmt.Errorf("RoleList: Missing required field: names") + } + return nil +} + +// +// RoleAuditLog - An audit log entry for role membership change. +// +type RoleAuditLog struct { + + // + // name of the role member + // + Member ResourceName `json:"member"` + + // + // name of the principal executing the change + // + Admin ResourceName `json:"admin"` + + // + // timestamp of the entry + // + Created rdl.Timestamp `json:"created"` + + // + // log action - either add or delete + // + Action string `json:"action"` + + // + // audit reference string for the change as supplied by admin + // + AuditRef string `json:"auditRef,omitempty" rdl:"optional"` +} + +// +// NewRoleAuditLog - creates an initialized RoleAuditLog instance, returns a pointer to it +// +func NewRoleAuditLog(init ...*RoleAuditLog) *RoleAuditLog { + var o *RoleAuditLog + if len(init) == 1 { + o = init[0] + } else { + o = new(RoleAuditLog) + } + return o +} + +type rawRoleAuditLog RoleAuditLog + +// +// UnmarshalJSON is defined for proper JSON decoding of a RoleAuditLog +// +func (pTypeDef *RoleAuditLog) UnmarshalJSON(b []byte) error { + var r rawRoleAuditLog + err := json.Unmarshal(b, &r) + if err == nil { + o := RoleAuditLog(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *RoleAuditLog) Validate() error { + if pTypeDef.Member == "" { + return fmt.Errorf("RoleAuditLog.member is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "ResourceName", pTypeDef.Member) + if !val.Valid { + return fmt.Errorf("RoleAuditLog.member does not contain a valid ResourceName (%v)", val.Error) + } + } + if pTypeDef.Admin == "" { + return fmt.Errorf("RoleAuditLog.admin is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "ResourceName", pTypeDef.Admin) + if !val.Valid { + return fmt.Errorf("RoleAuditLog.admin does not contain a valid ResourceName (%v)", val.Error) + } + } + if pTypeDef.Created.IsZero() { + return fmt.Errorf("RoleAuditLog: Missing required field: created") + } + if pTypeDef.Action == "" { + return fmt.Errorf("RoleAuditLog.action is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "String", pTypeDef.Action) + if !val.Valid { + return fmt.Errorf("RoleAuditLog.action does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// Role - The representation for a Role with set of members. +// +type Role struct { + + // + // name of the role + // + Name ResourceName `json:"name"` + + // + // last modification timestamp of the role + // + Modified *rdl.Timestamp `json:"modified,omitempty" rdl:"optional"` + + // + // an explicit list of members. Might be empty or null, if trust is set + // + Members []ResourceName `json:"members,omitempty" rdl:"optional"` + + // + // a trusted domain to delegate membership decisions to + // + Trust DomainName `json:"trust,omitempty" rdl:"optional"` + + // + // an audit log for role membership changes + // + AuditLog []*RoleAuditLog `json:"auditLog,omitempty" rdl:"optional"` +} + +// +// NewRole - creates an initialized Role instance, returns a pointer to it +// +func NewRole(init ...*Role) *Role { + var o *Role + if len(init) == 1 { + o = init[0] + } else { + o = new(Role) + } + return o +} + +type rawRole Role + +// +// UnmarshalJSON is defined for proper JSON decoding of a Role +// +func (pTypeDef *Role) UnmarshalJSON(b []byte) error { + var r rawRole + err := json.Unmarshal(b, &r) + if err == nil { + o := Role(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Role) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("Role.name is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "ResourceName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("Role.name does not contain a valid ResourceName (%v)", val.Error) + } + } + return nil +} + +// +// Roles - The representation for a list of roles with full details +// +type Roles struct { + + // + // list of role objects + // + List []*Role `json:"list"` +} + +// +// NewRoles - creates an initialized Roles instance, returns a pointer to it +// +func NewRoles(init ...*Roles) *Roles { + var o *Roles + if len(init) == 1 { + o = init[0] + } else { + o = new(Roles) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *Roles) Init() *Roles { + if pTypeDef.List == nil { + pTypeDef.List = make([]*Role, 0) + } + return pTypeDef +} + +type rawRoles Roles + +// +// UnmarshalJSON is defined for proper JSON decoding of a Roles +// +func (pTypeDef *Roles) UnmarshalJSON(b []byte) error { + var r rawRoles + err := json.Unmarshal(b, &r) + if err == nil { + o := Roles(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Roles) Validate() error { + if pTypeDef.List == nil { + return fmt.Errorf("Roles: Missing required field: list") + } + return nil +} + +// +// Membership - The representation for a role membership. +// +type Membership struct { + + // + // name of the member + // + MemberName ResourceName `json:"memberName"` + + // + // flag to indicate whether or the user is a member or not + // + IsMember *bool `json:"isMember,omitempty" rdl:"optional"` + + // + // name of the role + // + RoleName ResourceName `json:"roleName,omitempty" rdl:"optional"` +} + +// +// NewMembership - creates an initialized Membership instance, returns a pointer to it +// +func NewMembership(init ...*Membership) *Membership { + var o *Membership + if len(init) == 1 { + o = init[0] + } else { + o = new(Membership) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *Membership) Init() *Membership { + if pTypeDef.IsMember == nil { + d := true + pTypeDef.IsMember = &d + } + return pTypeDef +} + +type rawMembership Membership + +// +// UnmarshalJSON is defined for proper JSON decoding of a Membership +// +func (pTypeDef *Membership) UnmarshalJSON(b []byte) error { + var r rawMembership + err := json.Unmarshal(b, &r) + if err == nil { + o := Membership(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Membership) Validate() error { + if pTypeDef.MemberName == "" { + return fmt.Errorf("Membership.memberName is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "ResourceName", pTypeDef.MemberName) + if !val.Valid { + return fmt.Errorf("Membership.memberName does not contain a valid ResourceName (%v)", val.Error) + } + } + return nil +} + +// +// DefaultAdmins - The list of domain administrators. +// +type DefaultAdmins struct { + + // + // list of domain administrators + // + Admins []ResourceName `json:"admins"` +} + +// +// NewDefaultAdmins - creates an initialized DefaultAdmins instance, returns a pointer to it +// +func NewDefaultAdmins(init ...*DefaultAdmins) *DefaultAdmins { + var o *DefaultAdmins + if len(init) == 1 { + o = init[0] + } else { + o = new(DefaultAdmins) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *DefaultAdmins) Init() *DefaultAdmins { + if pTypeDef.Admins == nil { + pTypeDef.Admins = make([]ResourceName, 0) + } + return pTypeDef +} + +type rawDefaultAdmins DefaultAdmins + +// +// UnmarshalJSON is defined for proper JSON decoding of a DefaultAdmins +// +func (pTypeDef *DefaultAdmins) UnmarshalJSON(b []byte) error { + var r rawDefaultAdmins + err := json.Unmarshal(b, &r) + if err == nil { + o := DefaultAdmins(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DefaultAdmins) Validate() error { + if pTypeDef.Admins == nil { + return fmt.Errorf("DefaultAdmins: Missing required field: admins") + } + return nil +} + +// +// AssertionEffect - Every assertion can have the effect of ALLOW or DENY. +// +type AssertionEffect int + +// +// AssertionEffect constants +// +const ( + _ AssertionEffect = iota + ALLOW + DENY +) + +var namesAssertionEffect = []string{ + ALLOW: "ALLOW", + DENY: "DENY", +} + +// +// NewAssertionEffect - return a string representation of the enum +// +func NewAssertionEffect(init ...interface{}) AssertionEffect { + if len(init) == 1 { + switch v := init[0].(type) { + case AssertionEffect: + return v + case int: + return AssertionEffect(v) + case int32: + return AssertionEffect(v) + case string: + for i, s := range namesAssertionEffect { + if s == v { + return AssertionEffect(i) + } + } + default: + panic("Bad init value for AssertionEffect enum") + } + } + return AssertionEffect(0) //default to the first enum value +} + +// +// String - return a string representation of the enum +// +func (e AssertionEffect) String() string { + return namesAssertionEffect[e] +} + +// +// SymbolSet - return an array of all valid string representations (symbols) of the enum +// +func (e AssertionEffect) SymbolSet() []string { + return namesAssertionEffect +} + +// +// MarshalJSON is defined for proper JSON encoding of a AssertionEffect +// +func (e AssertionEffect) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +// +// UnmarshalJSON is defined for proper JSON decoding of a AssertionEffect +// +func (e *AssertionEffect) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err == nil { + s := string(j) + for v, s2 := range namesAssertionEffect { + if s == s2 { + *e = AssertionEffect(v) + return nil + } + } + err = fmt.Errorf("Bad enum symbol for type AssertionEffect: %s", s) + } + return err +} + +// +// Assertion - A representation for the encapsulation of an action to be +// performed on a resource by a principal. +// +type Assertion struct { + + // + // the subject of the assertion - a role + // + Role string `json:"role"` + + // + // the object of the assertion. Must be in the local namespace. Can contain + // wildcards + // + Resource string `json:"resource"` + + // + // the predicate of the assertion. Can contain wildcards + // + Action string `json:"action"` + + // + // the effect of the assertion in the policy language + // + Effect *AssertionEffect `json:"effect,omitempty" rdl:"optional"` + + // + // assertion id - auto generated by server. Not required during put + // operations. + // + Id *int64 `json:"id,omitempty" rdl:"optional"` +} + +// +// NewAssertion - creates an initialized Assertion instance, returns a pointer to it +// +func NewAssertion(init ...*Assertion) *Assertion { + var o *Assertion + if len(init) == 1 { + o = init[0] + } else { + o = new(Assertion) + } + return o +} + +type rawAssertion Assertion + +// +// UnmarshalJSON is defined for proper JSON decoding of a Assertion +// +func (pTypeDef *Assertion) UnmarshalJSON(b []byte) error { + var r rawAssertion + err := json.Unmarshal(b, &r) + if err == nil { + o := Assertion(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Assertion) Validate() error { + if pTypeDef.Role == "" { + return fmt.Errorf("Assertion.role is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "String", pTypeDef.Role) + if !val.Valid { + return fmt.Errorf("Assertion.role does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Resource == "" { + return fmt.Errorf("Assertion.resource is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "String", pTypeDef.Resource) + if !val.Valid { + return fmt.Errorf("Assertion.resource does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Action == "" { + return fmt.Errorf("Assertion.action is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "String", pTypeDef.Action) + if !val.Valid { + return fmt.Errorf("Assertion.action does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// Policy - The representation for a Policy with set of assertions. +// +type Policy struct { + + // + // name of the policy + // + Name ResourceName `json:"name"` + + // + // last modification timestamp of this policy + // + Modified *rdl.Timestamp `json:"modified,omitempty" rdl:"optional"` + + // + // list of defined assertions for this policy + // + Assertions []*Assertion `json:"assertions"` +} + +// +// NewPolicy - creates an initialized Policy instance, returns a pointer to it +// +func NewPolicy(init ...*Policy) *Policy { + var o *Policy + if len(init) == 1 { + o = init[0] + } else { + o = new(Policy) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *Policy) Init() *Policy { + if pTypeDef.Assertions == nil { + pTypeDef.Assertions = make([]*Assertion, 0) + } + return pTypeDef +} + +type rawPolicy Policy + +// +// UnmarshalJSON is defined for proper JSON decoding of a Policy +// +func (pTypeDef *Policy) UnmarshalJSON(b []byte) error { + var r rawPolicy + err := json.Unmarshal(b, &r) + if err == nil { + o := Policy(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Policy) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("Policy.name is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "ResourceName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("Policy.name does not contain a valid ResourceName (%v)", val.Error) + } + } + if pTypeDef.Assertions == nil { + return fmt.Errorf("Policy: Missing required field: assertions") + } + return nil +} + +// +// Policies - The representation of list of policy objects +// +type Policies struct { + + // + // list of policy objects + // + List []*Policy `json:"list"` +} + +// +// NewPolicies - creates an initialized Policies instance, returns a pointer to it +// +func NewPolicies(init ...*Policies) *Policies { + var o *Policies + if len(init) == 1 { + o = init[0] + } else { + o = new(Policies) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *Policies) Init() *Policies { + if pTypeDef.List == nil { + pTypeDef.List = make([]*Policy, 0) + } + return pTypeDef +} + +type rawPolicies Policies + +// +// UnmarshalJSON is defined for proper JSON decoding of a Policies +// +func (pTypeDef *Policies) UnmarshalJSON(b []byte) error { + var r rawPolicies + err := json.Unmarshal(b, &r) + if err == nil { + o := Policies(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Policies) Validate() error { + if pTypeDef.List == nil { + return fmt.Errorf("Policies: Missing required field: list") + } + return nil +} + +// +// Template - Solution Template object defined on the server +// +type Template struct { + + // + // list of roles in the template + // + Roles []*Role `json:"roles"` + + // + // list of policies defined in this template + // + Policies []*Policy `json:"policies"` +} + +// +// NewTemplate - creates an initialized Template instance, returns a pointer to it +// +func NewTemplate(init ...*Template) *Template { + var o *Template + if len(init) == 1 { + o = init[0] + } else { + o = new(Template) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *Template) Init() *Template { + if pTypeDef.Roles == nil { + pTypeDef.Roles = make([]*Role, 0) + } + if pTypeDef.Policies == nil { + pTypeDef.Policies = make([]*Policy, 0) + } + return pTypeDef +} + +type rawTemplate Template + +// +// UnmarshalJSON is defined for proper JSON decoding of a Template +// +func (pTypeDef *Template) UnmarshalJSON(b []byte) error { + var r rawTemplate + err := json.Unmarshal(b, &r) + if err == nil { + o := Template(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Template) Validate() error { + if pTypeDef.Roles == nil { + return fmt.Errorf("Template: Missing required field: roles") + } + if pTypeDef.Policies == nil { + return fmt.Errorf("Template: Missing required field: policies") + } + return nil +} + +// +// TemplateList - List of template names that is the base struct for server and +// domain templates +// +type TemplateList struct { + + // + // list of template names + // + TemplateNames []SimpleName `json:"templateNames"` +} + +// +// NewTemplateList - creates an initialized TemplateList instance, returns a pointer to it +// +func NewTemplateList(init ...*TemplateList) *TemplateList { + var o *TemplateList + if len(init) == 1 { + o = init[0] + } else { + o = new(TemplateList) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *TemplateList) Init() *TemplateList { + if pTypeDef.TemplateNames == nil { + pTypeDef.TemplateNames = make([]SimpleName, 0) + } + return pTypeDef +} + +type rawTemplateList TemplateList + +// +// UnmarshalJSON is defined for proper JSON decoding of a TemplateList +// +func (pTypeDef *TemplateList) UnmarshalJSON(b []byte) error { + var r rawTemplateList + err := json.Unmarshal(b, &r) + if err == nil { + o := TemplateList(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *TemplateList) Validate() error { + if pTypeDef.TemplateNames == nil { + return fmt.Errorf("TemplateList: Missing required field: templateNames") + } + return nil +} + +// +// DomainTemplate - solution template(s) to be applied to a domain +// +type DomainTemplate struct { + + // + // list of template names + // + TemplateNames []SimpleName `json:"templateNames"` +} + +// +// NewDomainTemplate - creates an initialized DomainTemplate instance, returns a pointer to it +// +func NewDomainTemplate(init ...*DomainTemplate) *DomainTemplate { + var o *DomainTemplate + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainTemplate) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *DomainTemplate) Init() *DomainTemplate { + if pTypeDef.TemplateNames == nil { + pTypeDef.TemplateNames = make([]SimpleName, 0) + } + return pTypeDef +} + +type rawDomainTemplate DomainTemplate + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainTemplate +// +func (pTypeDef *DomainTemplate) UnmarshalJSON(b []byte) error { + var r rawDomainTemplate + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainTemplate(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainTemplate) Validate() error { + if pTypeDef.TemplateNames == nil { + return fmt.Errorf("DomainTemplate: Missing required field: templateNames") + } + return nil +} + +// +// DomainTemplateList - List of solution templates to be applied to a domain +// +type DomainTemplateList struct { + + // + // list of template names + // + TemplateNames []SimpleName `json:"templateNames"` +} + +// +// NewDomainTemplateList - creates an initialized DomainTemplateList instance, returns a pointer to it +// +func NewDomainTemplateList(init ...*DomainTemplateList) *DomainTemplateList { + var o *DomainTemplateList + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainTemplateList) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *DomainTemplateList) Init() *DomainTemplateList { + if pTypeDef.TemplateNames == nil { + pTypeDef.TemplateNames = make([]SimpleName, 0) + } + return pTypeDef +} + +type rawDomainTemplateList DomainTemplateList + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainTemplateList +// +func (pTypeDef *DomainTemplateList) UnmarshalJSON(b []byte) error { + var r rawDomainTemplateList + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainTemplateList(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainTemplateList) Validate() error { + if pTypeDef.TemplateNames == nil { + return fmt.Errorf("DomainTemplateList: Missing required field: templateNames") + } + return nil +} + +// +// ServerTemplateList - List of solution templates available in the server +// +type ServerTemplateList struct { + + // + // list of template names + // + TemplateNames []SimpleName `json:"templateNames"` +} + +// +// NewServerTemplateList - creates an initialized ServerTemplateList instance, returns a pointer to it +// +func NewServerTemplateList(init ...*ServerTemplateList) *ServerTemplateList { + var o *ServerTemplateList + if len(init) == 1 { + o = init[0] + } else { + o = new(ServerTemplateList) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *ServerTemplateList) Init() *ServerTemplateList { + if pTypeDef.TemplateNames == nil { + pTypeDef.TemplateNames = make([]SimpleName, 0) + } + return pTypeDef +} + +type rawServerTemplateList ServerTemplateList + +// +// UnmarshalJSON is defined for proper JSON decoding of a ServerTemplateList +// +func (pTypeDef *ServerTemplateList) UnmarshalJSON(b []byte) error { + var r rawServerTemplateList + err := json.Unmarshal(b, &r) + if err == nil { + o := ServerTemplateList(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *ServerTemplateList) Validate() error { + if pTypeDef.TemplateNames == nil { + return fmt.Errorf("ServerTemplateList: Missing required field: templateNames") + } + return nil +} + +// +// DomainList - A paginated list of domains. +// +type DomainList struct { + + // + // list of domain names + // + Names []DomainName `json:"names"` + + // + // if the response is a paginated list, this attribute specifies the value to + // be used in the next domain list request as the value for the skip query + // parameter. + // + Next string `json:"next,omitempty" rdl:"optional"` +} + +// +// NewDomainList - creates an initialized DomainList instance, returns a pointer to it +// +func NewDomainList(init ...*DomainList) *DomainList { + var o *DomainList + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainList) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *DomainList) Init() *DomainList { + if pTypeDef.Names == nil { + pTypeDef.Names = make([]DomainName, 0) + } + return pTypeDef +} + +type rawDomainList DomainList + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainList +// +func (pTypeDef *DomainList) UnmarshalJSON(b []byte) error { + var r rawDomainList + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainList(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainList) Validate() error { + if pTypeDef.Names == nil { + return fmt.Errorf("DomainList: Missing required field: names") + } + return nil +} + +// +// DomainMeta - Set of metadata attributes that all domains may have and can be +// changed. +// +type DomainMeta struct { + + // + // a description of the domain + // + Description string `json:"description,omitempty" rdl:"optional"` + + // + // a reference to an Organization. (i.e. org:media) + // + Org ResourceName `json:"org,omitempty" rdl:"optional"` + + // + // Future use only, currently not used + // + Enabled *bool `json:"enabled,omitempty" rdl:"optional"` + + // + // Flag indicates whether or not domain modifications should be logged for + // SOX+Auditing. If true, the auditRef parameter must be supplied(not empty) for + // any API defining it. + // + AuditEnabled *bool `json:"auditEnabled,omitempty" rdl:"optional"` + + // + // associated cloud (i.e. aws) account id + // + Account string `json:"account,omitempty" rdl:"optional"` + + // + // associated product id + // + YpmId *int32 `json:"ypmId,omitempty" rdl:"optional"` +} + +// +// NewDomainMeta - creates an initialized DomainMeta instance, returns a pointer to it +// +func NewDomainMeta(init ...*DomainMeta) *DomainMeta { + var o *DomainMeta + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainMeta) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *DomainMeta) Init() *DomainMeta { + if pTypeDef.Enabled == nil { + d := true + pTypeDef.Enabled = &d + } + if pTypeDef.AuditEnabled == nil { + d := false + pTypeDef.AuditEnabled = &d + } + return pTypeDef +} + +type rawDomainMeta DomainMeta + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainMeta +// +func (pTypeDef *DomainMeta) UnmarshalJSON(b []byte) error { + var r rawDomainMeta + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainMeta(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainMeta) Validate() error { + return nil +} + +// +// TopLevelDomain - Top Level Domain object. The required attributes include +// the name of the domain and list of domain administrators. +// +type TopLevelDomain struct { + + // + // a description of the domain + // + Description string `json:"description,omitempty" rdl:"optional"` + + // + // a reference to an Organization. (i.e. org:media) + // + Org ResourceName `json:"org,omitempty" rdl:"optional"` + + // + // Future use only, currently not used + // + Enabled *bool `json:"enabled,omitempty" rdl:"optional"` + + // + // Flag indicates whether or not domain modifications should be logged for + // SOX+Auditing. If true, the auditRef parameter must be supplied(not empty) for + // any API defining it. + // + AuditEnabled *bool `json:"auditEnabled,omitempty" rdl:"optional"` + + // + // associated cloud (i.e. aws) account id + // + Account string `json:"account,omitempty" rdl:"optional"` + + // + // associated product id + // + YpmId *int32 `json:"ypmId,omitempty" rdl:"optional"` + + // + // name of the domain + // + Name SimpleName `json:"name"` + + // + // list of domain administrators + // + AdminUsers []ResourceName `json:"adminUsers"` + + // + // list of solution template names + // + Templates *DomainTemplateList `json:"templates,omitempty" rdl:"optional"` +} + +// +// NewTopLevelDomain - creates an initialized TopLevelDomain instance, returns a pointer to it +// +func NewTopLevelDomain(init ...*TopLevelDomain) *TopLevelDomain { + var o *TopLevelDomain + if len(init) == 1 { + o = init[0] + } else { + o = new(TopLevelDomain) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *TopLevelDomain) Init() *TopLevelDomain { + if pTypeDef.Enabled == nil { + d := true + pTypeDef.Enabled = &d + } + if pTypeDef.AuditEnabled == nil { + d := false + pTypeDef.AuditEnabled = &d + } + if pTypeDef.AdminUsers == nil { + pTypeDef.AdminUsers = make([]ResourceName, 0) + } + return pTypeDef +} + +type rawTopLevelDomain TopLevelDomain + +// +// UnmarshalJSON is defined for proper JSON decoding of a TopLevelDomain +// +func (pTypeDef *TopLevelDomain) UnmarshalJSON(b []byte) error { + var r rawTopLevelDomain + err := json.Unmarshal(b, &r) + if err == nil { + o := TopLevelDomain(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *TopLevelDomain) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("TopLevelDomain.name is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "SimpleName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("TopLevelDomain.name does not contain a valid SimpleName (%v)", val.Error) + } + } + if pTypeDef.AdminUsers == nil { + return fmt.Errorf("TopLevelDomain: Missing required field: adminUsers") + } + return nil +} + +// +// SubDomain - A Subdomain is a TopLevelDomain, except it has a parent. +// +type SubDomain struct { + + // + // a description of the domain + // + Description string `json:"description,omitempty" rdl:"optional"` + + // + // a reference to an Organization. (i.e. org:media) + // + Org ResourceName `json:"org,omitempty" rdl:"optional"` + + // + // Future use only, currently not used + // + Enabled *bool `json:"enabled,omitempty" rdl:"optional"` + + // + // Flag indicates whether or not domain modifications should be logged for + // SOX+Auditing. If true, the auditRef parameter must be supplied(not empty) for + // any API defining it. + // + AuditEnabled *bool `json:"auditEnabled,omitempty" rdl:"optional"` + + // + // associated cloud (i.e. aws) account id + // + Account string `json:"account,omitempty" rdl:"optional"` + + // + // associated product id + // + YpmId *int32 `json:"ypmId,omitempty" rdl:"optional"` + + // + // name of the domain + // + Name SimpleName `json:"name"` + + // + // list of domain administrators + // + AdminUsers []ResourceName `json:"adminUsers"` + + // + // list of solution template names + // + Templates *DomainTemplateList `json:"templates,omitempty" rdl:"optional"` + + // + // name of the parent domain + // + Parent DomainName `json:"parent"` +} + +// +// NewSubDomain - creates an initialized SubDomain instance, returns a pointer to it +// +func NewSubDomain(init ...*SubDomain) *SubDomain { + var o *SubDomain + if len(init) == 1 { + o = init[0] + } else { + o = new(SubDomain) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *SubDomain) Init() *SubDomain { + if pTypeDef.Enabled == nil { + d := true + pTypeDef.Enabled = &d + } + if pTypeDef.AuditEnabled == nil { + d := false + pTypeDef.AuditEnabled = &d + } + if pTypeDef.AdminUsers == nil { + pTypeDef.AdminUsers = make([]ResourceName, 0) + } + return pTypeDef +} + +type rawSubDomain SubDomain + +// +// UnmarshalJSON is defined for proper JSON decoding of a SubDomain +// +func (pTypeDef *SubDomain) UnmarshalJSON(b []byte) error { + var r rawSubDomain + err := json.Unmarshal(b, &r) + if err == nil { + o := SubDomain(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *SubDomain) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("SubDomain.name is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "SimpleName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("SubDomain.name does not contain a valid SimpleName (%v)", val.Error) + } + } + if pTypeDef.AdminUsers == nil { + return fmt.Errorf("SubDomain: Missing required field: adminUsers") + } + if pTypeDef.Parent == "" { + return fmt.Errorf("SubDomain.parent is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Parent) + if !val.Valid { + return fmt.Errorf("SubDomain.parent does not contain a valid DomainName (%v)", val.Error) + } + } + return nil +} + +// +// UserDomain - A UserDomain is the user's own top level domain in user - e.g. +// user.hga +// +type UserDomain struct { + + // + // a description of the domain + // + Description string `json:"description,omitempty" rdl:"optional"` + + // + // a reference to an Organization. (i.e. org:media) + // + Org ResourceName `json:"org,omitempty" rdl:"optional"` + + // + // Future use only, currently not used + // + Enabled *bool `json:"enabled,omitempty" rdl:"optional"` + + // + // Flag indicates whether or not domain modifications should be logged for + // SOX+Auditing. If true, the auditRef parameter must be supplied(not empty) for + // any API defining it. + // + AuditEnabled *bool `json:"auditEnabled,omitempty" rdl:"optional"` + + // + // associated cloud (i.e. aws) account id + // + Account string `json:"account,omitempty" rdl:"optional"` + + // + // associated product id + // + YpmId *int32 `json:"ypmId,omitempty" rdl:"optional"` + + // + // user id which will be the domain name + // + Name SimpleName `json:"name"` + + // + // list of solution template names + // + Templates *DomainTemplateList `json:"templates,omitempty" rdl:"optional"` +} + +// +// NewUserDomain - creates an initialized UserDomain instance, returns a pointer to it +// +func NewUserDomain(init ...*UserDomain) *UserDomain { + var o *UserDomain + if len(init) == 1 { + o = init[0] + } else { + o = new(UserDomain) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *UserDomain) Init() *UserDomain { + if pTypeDef.Enabled == nil { + d := true + pTypeDef.Enabled = &d + } + if pTypeDef.AuditEnabled == nil { + d := false + pTypeDef.AuditEnabled = &d + } + return pTypeDef +} + +type rawUserDomain UserDomain + +// +// UnmarshalJSON is defined for proper JSON decoding of a UserDomain +// +func (pTypeDef *UserDomain) UnmarshalJSON(b []byte) error { + var r rawUserDomain + err := json.Unmarshal(b, &r) + if err == nil { + o := UserDomain(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *UserDomain) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("UserDomain.name is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "SimpleName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("UserDomain.name does not contain a valid SimpleName (%v)", val.Error) + } + } + return nil +} + +// +// DanglingPolicy - A dangling policy where the assertion is referencing a role +// name that doesn't exist in the domain +// +type DanglingPolicy struct { + PolicyName EntityName `json:"policyName"` + RoleName EntityName `json:"roleName"` +} + +// +// NewDanglingPolicy - creates an initialized DanglingPolicy instance, returns a pointer to it +// +func NewDanglingPolicy(init ...*DanglingPolicy) *DanglingPolicy { + var o *DanglingPolicy + if len(init) == 1 { + o = init[0] + } else { + o = new(DanglingPolicy) + } + return o +} + +type rawDanglingPolicy DanglingPolicy + +// +// UnmarshalJSON is defined for proper JSON decoding of a DanglingPolicy +// +func (pTypeDef *DanglingPolicy) UnmarshalJSON(b []byte) error { + var r rawDanglingPolicy + err := json.Unmarshal(b, &r) + if err == nil { + o := DanglingPolicy(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DanglingPolicy) Validate() error { + if pTypeDef.PolicyName == "" { + return fmt.Errorf("DanglingPolicy.policyName is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "EntityName", pTypeDef.PolicyName) + if !val.Valid { + return fmt.Errorf("DanglingPolicy.policyName does not contain a valid EntityName (%v)", val.Error) + } + } + if pTypeDef.RoleName == "" { + return fmt.Errorf("DanglingPolicy.roleName is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "EntityName", pTypeDef.RoleName) + if !val.Valid { + return fmt.Errorf("DanglingPolicy.roleName does not contain a valid EntityName (%v)", val.Error) + } + } + return nil +} + +// +// DomainDataCheck - Domain data object representing the results of a check +// operation looking for dangling roles, policies and trust relationships that +// are set either on tenant or provider side only +// +type DomainDataCheck struct { + + // + // Names of roles not specified in any assertion. Might be empty or null if no + // dangling roles. + // + DanglingRoles []EntityName `json:"danglingRoles,omitempty" rdl:"optional"` + + // + // Policy+role tuples where role doesnt exist. Might be empty or null if no + // dangling policies. + // + DanglingPolicies []*DanglingPolicy `json:"danglingPolicies,omitempty" rdl:"optional"` + + // + // total number of policies + // + PolicyCount int32 `json:"policyCount"` + + // + // total number of assertions + // + AssertionCount int32 `json:"assertionCount"` + + // + // total number of assertions containing roles as wildcards + // + RoleWildCardCount int32 `json:"roleWildCardCount"` + + // + // Service names (domain.service) that dont contain trust role if this is a + // tenant domain. Might be empty or null, if not a tenant or if all providers + // support this tenant. + // + ProvidersWithoutTrust []ServiceName `json:"providersWithoutTrust,omitempty" rdl:"optional"` + + // + // Names of Tenant domains that dont contain assume role assertions if this is + // a provider domain. Might be empty or null, if not a provider or if all + // tenants support use this provider. + // + TenantsWithoutAssumeRole []DomainName `json:"tenantsWithoutAssumeRole,omitempty" rdl:"optional"` +} + +// +// NewDomainDataCheck - creates an initialized DomainDataCheck instance, returns a pointer to it +// +func NewDomainDataCheck(init ...*DomainDataCheck) *DomainDataCheck { + var o *DomainDataCheck + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainDataCheck) + } + return o +} + +type rawDomainDataCheck DomainDataCheck + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainDataCheck +// +func (pTypeDef *DomainDataCheck) UnmarshalJSON(b []byte) error { + var r rawDomainDataCheck + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainDataCheck(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainDataCheck) Validate() error { + return nil +} + +// +// Entity - An entity is a name and a structured value. some entity +// names/prefixes are reserved (i.e. "role", "policy", "meta", "domain", +// "service") +// +type Entity struct { + + // + // name of the entity object + // + Name EntityName `json:"name"` + + // + // value of the entity + // + Value rdl.Struct `json:"value"` +} + +// +// NewEntity - creates an initialized Entity instance, returns a pointer to it +// +func NewEntity(init ...*Entity) *Entity { + var o *Entity + if len(init) == 1 { + o = init[0] + } else { + o = new(Entity) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *Entity) Init() *Entity { + if pTypeDef.Value == nil { + pTypeDef.Value = make(rdl.Struct) + } + return pTypeDef +} + +type rawEntity Entity + +// +// UnmarshalJSON is defined for proper JSON decoding of a Entity +// +func (pTypeDef *Entity) UnmarshalJSON(b []byte) error { + var r rawEntity + err := json.Unmarshal(b, &r) + if err == nil { + o := Entity(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Entity) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("Entity.name is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "EntityName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("Entity.name does not contain a valid EntityName (%v)", val.Error) + } + } + if pTypeDef.Value == nil { + return fmt.Errorf("Entity: Missing required field: value") + } + return nil +} + +// +// EntityList - The representation for an enumeration of entities in the +// namespace +// +type EntityList struct { + + // + // list of entity names + // + Names []EntityName `json:"names"` +} + +// +// NewEntityList - creates an initialized EntityList instance, returns a pointer to it +// +func NewEntityList(init ...*EntityList) *EntityList { + var o *EntityList + if len(init) == 1 { + o = init[0] + } else { + o = new(EntityList) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *EntityList) Init() *EntityList { + if pTypeDef.Names == nil { + pTypeDef.Names = make([]EntityName, 0) + } + return pTypeDef +} + +type rawEntityList EntityList + +// +// UnmarshalJSON is defined for proper JSON decoding of a EntityList +// +func (pTypeDef *EntityList) UnmarshalJSON(b []byte) error { + var r rawEntityList + err := json.Unmarshal(b, &r) + if err == nil { + o := EntityList(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *EntityList) Validate() error { + if pTypeDef.Names == nil { + return fmt.Errorf("EntityList: Missing required field: names") + } + return nil +} + +// +// PolicyList - The representation for an enumeration of policies in the +// namespace, with pagination. +// +type PolicyList struct { + + // + // list of policy names + // + Names []EntityName `json:"names"` + + // + // if the response is a paginated list, this attribute specifies the value to + // be used in the next policy list request as the value for the skip query + // parameter. + // + Next string `json:"next,omitempty" rdl:"optional"` +} + +// +// NewPolicyList - creates an initialized PolicyList instance, returns a pointer to it +// +func NewPolicyList(init ...*PolicyList) *PolicyList { + var o *PolicyList + if len(init) == 1 { + o = init[0] + } else { + o = new(PolicyList) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *PolicyList) Init() *PolicyList { + if pTypeDef.Names == nil { + pTypeDef.Names = make([]EntityName, 0) + } + return pTypeDef +} + +type rawPolicyList PolicyList + +// +// UnmarshalJSON is defined for proper JSON decoding of a PolicyList +// +func (pTypeDef *PolicyList) UnmarshalJSON(b []byte) error { + var r rawPolicyList + err := json.Unmarshal(b, &r) + if err == nil { + o := PolicyList(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *PolicyList) Validate() error { + if pTypeDef.Names == nil { + return fmt.Errorf("PolicyList: Missing required field: names") + } + return nil +} + +// +// PublicKeyEntry - The representation of the public key in a service identity +// object. +// +type PublicKeyEntry struct { + + // + // the public key for the service + // + Key string `json:"key"` + + // + // the key identifier (version or zone name) + // + Id string `json:"id"` +} + +// +// NewPublicKeyEntry - creates an initialized PublicKeyEntry instance, returns a pointer to it +// +func NewPublicKeyEntry(init ...*PublicKeyEntry) *PublicKeyEntry { + var o *PublicKeyEntry + if len(init) == 1 { + o = init[0] + } else { + o = new(PublicKeyEntry) + } + return o +} + +type rawPublicKeyEntry PublicKeyEntry + +// +// UnmarshalJSON is defined for proper JSON decoding of a PublicKeyEntry +// +func (pTypeDef *PublicKeyEntry) UnmarshalJSON(b []byte) error { + var r rawPublicKeyEntry + err := json.Unmarshal(b, &r) + if err == nil { + o := PublicKeyEntry(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *PublicKeyEntry) Validate() error { + if pTypeDef.Key == "" { + return fmt.Errorf("PublicKeyEntry.key is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "String", pTypeDef.Key) + if !val.Valid { + return fmt.Errorf("PublicKeyEntry.key does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Id == "" { + return fmt.Errorf("PublicKeyEntry.id is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "String", pTypeDef.Id) + if !val.Valid { + return fmt.Errorf("PublicKeyEntry.id does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// ServiceIdentity - The representation of the service identity object. +// +type ServiceIdentity struct { + + // + // the full name of the service, i.e. "sports.storage" + // + Name ServiceName `json:"name"` + + // + // array of public keys for key rotation + // + PublicKeys []*PublicKeyEntry `json:"publicKeys,omitempty" rdl:"optional"` + + // + // if present, then this service can provision tenants via this endpoint. + // + ProviderEndpoint string `json:"providerEndpoint,omitempty" rdl:"optional"` + + // + // the timestamp when this entry was last modified + // + Modified *rdl.Timestamp `json:"modified,omitempty" rdl:"optional"` + + // + // the path of the executable that runs the service + // + Executable string `json:"executable,omitempty" rdl:"optional"` + + // + // list of host names that this service can run on + // + Hosts []string `json:"hosts,omitempty" rdl:"optional"` + + // + // local (unix) user name this service can run as + // + User string `json:"user,omitempty" rdl:"optional"` + + // + // local (unix) group name this service can run as + // + Group string `json:"group,omitempty" rdl:"optional"` +} + +// +// NewServiceIdentity - creates an initialized ServiceIdentity instance, returns a pointer to it +// +func NewServiceIdentity(init ...*ServiceIdentity) *ServiceIdentity { + var o *ServiceIdentity + if len(init) == 1 { + o = init[0] + } else { + o = new(ServiceIdentity) + } + return o +} + +type rawServiceIdentity ServiceIdentity + +// +// UnmarshalJSON is defined for proper JSON decoding of a ServiceIdentity +// +func (pTypeDef *ServiceIdentity) UnmarshalJSON(b []byte) error { + var r rawServiceIdentity + err := json.Unmarshal(b, &r) + if err == nil { + o := ServiceIdentity(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *ServiceIdentity) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("ServiceIdentity.name is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "ServiceName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("ServiceIdentity.name does not contain a valid ServiceName (%v)", val.Error) + } + } + return nil +} + +// +// ServiceIdentities - The representation of list of services +// +type ServiceIdentities struct { + + // + // list of services + // + List []*ServiceIdentity `json:"list"` +} + +// +// NewServiceIdentities - creates an initialized ServiceIdentities instance, returns a pointer to it +// +func NewServiceIdentities(init ...*ServiceIdentities) *ServiceIdentities { + var o *ServiceIdentities + if len(init) == 1 { + o = init[0] + } else { + o = new(ServiceIdentities) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *ServiceIdentities) Init() *ServiceIdentities { + if pTypeDef.List == nil { + pTypeDef.List = make([]*ServiceIdentity, 0) + } + return pTypeDef +} + +type rawServiceIdentities ServiceIdentities + +// +// UnmarshalJSON is defined for proper JSON decoding of a ServiceIdentities +// +func (pTypeDef *ServiceIdentities) UnmarshalJSON(b []byte) error { + var r rawServiceIdentities + err := json.Unmarshal(b, &r) + if err == nil { + o := ServiceIdentities(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *ServiceIdentities) Validate() error { + if pTypeDef.List == nil { + return fmt.Errorf("ServiceIdentities: Missing required field: list") + } + return nil +} + +// +// ServiceIdentityList - The representation for an enumeration of services in +// the namespace, with pagination. +// +type ServiceIdentityList struct { + + // + // list of service names + // + Names []EntityName `json:"names"` + + // + // if the response is a paginated list, this attribute specifies the value to + // be used in the next service list request as the value for the skip query + // parameter. + // + Next string `json:"next,omitempty" rdl:"optional"` +} + +// +// NewServiceIdentityList - creates an initialized ServiceIdentityList instance, returns a pointer to it +// +func NewServiceIdentityList(init ...*ServiceIdentityList) *ServiceIdentityList { + var o *ServiceIdentityList + if len(init) == 1 { + o = init[0] + } else { + o = new(ServiceIdentityList) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *ServiceIdentityList) Init() *ServiceIdentityList { + if pTypeDef.Names == nil { + pTypeDef.Names = make([]EntityName, 0) + } + return pTypeDef +} + +type rawServiceIdentityList ServiceIdentityList + +// +// UnmarshalJSON is defined for proper JSON decoding of a ServiceIdentityList +// +func (pTypeDef *ServiceIdentityList) UnmarshalJSON(b []byte) error { + var r rawServiceIdentityList + err := json.Unmarshal(b, &r) + if err == nil { + o := ServiceIdentityList(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *ServiceIdentityList) Validate() error { + if pTypeDef.Names == nil { + return fmt.Errorf("ServiceIdentityList: Missing required field: names") + } + return nil +} + +// +// Tenancy - A representation of tenant. +// +type Tenancy struct { + + // + // the domain that is to get a tenancy + // + Domain DomainName `json:"domain"` + + // + // the provider service on which the tenancy is to reside + // + Service ServiceName `json:"service"` + + // + // registered resource groups for this tenant + // + ResourceGroups []EntityName `json:"resourceGroups,omitempty" rdl:"optional"` +} + +// +// NewTenancy - creates an initialized Tenancy instance, returns a pointer to it +// +func NewTenancy(init ...*Tenancy) *Tenancy { + var o *Tenancy + if len(init) == 1 { + o = init[0] + } else { + o = new(Tenancy) + } + return o +} + +type rawTenancy Tenancy + +// +// UnmarshalJSON is defined for proper JSON decoding of a Tenancy +// +func (pTypeDef *Tenancy) UnmarshalJSON(b []byte) error { + var r rawTenancy + err := json.Unmarshal(b, &r) + if err == nil { + o := Tenancy(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Tenancy) Validate() error { + if pTypeDef.Domain == "" { + return fmt.Errorf("Tenancy.domain is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Domain) + if !val.Valid { + return fmt.Errorf("Tenancy.domain does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Service == "" { + return fmt.Errorf("Tenancy.service is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "ServiceName", pTypeDef.Service) + if !val.Valid { + return fmt.Errorf("Tenancy.service does not contain a valid ServiceName (%v)", val.Error) + } + } + return nil +} + +// +// TenancyResourceGroup - +// +type TenancyResourceGroup struct { + + // + // the domain that is to get a tenancy + // + Domain DomainName `json:"domain"` + + // + // the provider service on which the tenancy is to reside + // + Service ServiceName `json:"service"` + + // + // registered resource group for this tenant + // + ResourceGroup EntityName `json:"resourceGroup"` +} + +// +// NewTenancyResourceGroup - creates an initialized TenancyResourceGroup instance, returns a pointer to it +// +func NewTenancyResourceGroup(init ...*TenancyResourceGroup) *TenancyResourceGroup { + var o *TenancyResourceGroup + if len(init) == 1 { + o = init[0] + } else { + o = new(TenancyResourceGroup) + } + return o +} + +type rawTenancyResourceGroup TenancyResourceGroup + +// +// UnmarshalJSON is defined for proper JSON decoding of a TenancyResourceGroup +// +func (pTypeDef *TenancyResourceGroup) UnmarshalJSON(b []byte) error { + var r rawTenancyResourceGroup + err := json.Unmarshal(b, &r) + if err == nil { + o := TenancyResourceGroup(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *TenancyResourceGroup) Validate() error { + if pTypeDef.Domain == "" { + return fmt.Errorf("TenancyResourceGroup.domain is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Domain) + if !val.Valid { + return fmt.Errorf("TenancyResourceGroup.domain does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Service == "" { + return fmt.Errorf("TenancyResourceGroup.service is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "ServiceName", pTypeDef.Service) + if !val.Valid { + return fmt.Errorf("TenancyResourceGroup.service does not contain a valid ServiceName (%v)", val.Error) + } + } + if pTypeDef.ResourceGroup == "" { + return fmt.Errorf("TenancyResourceGroup.resourceGroup is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "EntityName", pTypeDef.ResourceGroup) + if !val.Valid { + return fmt.Errorf("TenancyResourceGroup.resourceGroup does not contain a valid EntityName (%v)", val.Error) + } + } + return nil +} + +// +// TenantRoleAction - A representation of tenant role action. +// +type TenantRoleAction struct { + + // + // name of the role + // + Role SimpleName `json:"role"` + + // + // action value for the generated policy assertion + // + Action string `json:"action"` +} + +// +// NewTenantRoleAction - creates an initialized TenantRoleAction instance, returns a pointer to it +// +func NewTenantRoleAction(init ...*TenantRoleAction) *TenantRoleAction { + var o *TenantRoleAction + if len(init) == 1 { + o = init[0] + } else { + o = new(TenantRoleAction) + } + return o +} + +type rawTenantRoleAction TenantRoleAction + +// +// UnmarshalJSON is defined for proper JSON decoding of a TenantRoleAction +// +func (pTypeDef *TenantRoleAction) UnmarshalJSON(b []byte) error { + var r rawTenantRoleAction + err := json.Unmarshal(b, &r) + if err == nil { + o := TenantRoleAction(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *TenantRoleAction) Validate() error { + if pTypeDef.Role == "" { + return fmt.Errorf("TenantRoleAction.role is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "SimpleName", pTypeDef.Role) + if !val.Valid { + return fmt.Errorf("TenantRoleAction.role does not contain a valid SimpleName (%v)", val.Error) + } + } + if pTypeDef.Action == "" { + return fmt.Errorf("TenantRoleAction.action is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "String", pTypeDef.Action) + if !val.Valid { + return fmt.Errorf("TenantRoleAction.action does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// TenantRoles - A representation of tenant roles to be provisioned. +// +type TenantRoles struct { + + // + // name of the provider domain + // + Domain DomainName `json:"domain"` + + // + // name of the provider service + // + Service SimpleName `json:"service"` + + // + // name of the tenant domain + // + Tenant DomainName `json:"tenant"` + + // + // the role/action pairs to provision + // + Roles []*TenantRoleAction `json:"roles"` +} + +// +// NewTenantRoles - creates an initialized TenantRoles instance, returns a pointer to it +// +func NewTenantRoles(init ...*TenantRoles) *TenantRoles { + var o *TenantRoles + if len(init) == 1 { + o = init[0] + } else { + o = new(TenantRoles) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *TenantRoles) Init() *TenantRoles { + if pTypeDef.Roles == nil { + pTypeDef.Roles = make([]*TenantRoleAction, 0) + } + return pTypeDef +} + +type rawTenantRoles TenantRoles + +// +// UnmarshalJSON is defined for proper JSON decoding of a TenantRoles +// +func (pTypeDef *TenantRoles) UnmarshalJSON(b []byte) error { + var r rawTenantRoles + err := json.Unmarshal(b, &r) + if err == nil { + o := TenantRoles(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *TenantRoles) Validate() error { + if pTypeDef.Domain == "" { + return fmt.Errorf("TenantRoles.domain is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Domain) + if !val.Valid { + return fmt.Errorf("TenantRoles.domain does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Service == "" { + return fmt.Errorf("TenantRoles.service is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "SimpleName", pTypeDef.Service) + if !val.Valid { + return fmt.Errorf("TenantRoles.service does not contain a valid SimpleName (%v)", val.Error) + } + } + if pTypeDef.Tenant == "" { + return fmt.Errorf("TenantRoles.tenant is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Tenant) + if !val.Valid { + return fmt.Errorf("TenantRoles.tenant does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Roles == nil { + return fmt.Errorf("TenantRoles: Missing required field: roles") + } + return nil +} + +// +// TenantResourceGroupRoles - A representation of tenant roles for resource +// groups to be provisioned. +// +type TenantResourceGroupRoles struct { + + // + // name of the provider domain + // + Domain DomainName `json:"domain"` + + // + // name of the provider service + // + Service SimpleName `json:"service"` + + // + // name of the tenant domain + // + Tenant DomainName `json:"tenant"` + + // + // the role/action pairs to provision + // + Roles []*TenantRoleAction `json:"roles"` + + // + // tenant resource group + // + ResourceGroup EntityName `json:"resourceGroup"` +} + +// +// NewTenantResourceGroupRoles - creates an initialized TenantResourceGroupRoles instance, returns a pointer to it +// +func NewTenantResourceGroupRoles(init ...*TenantResourceGroupRoles) *TenantResourceGroupRoles { + var o *TenantResourceGroupRoles + if len(init) == 1 { + o = init[0] + } else { + o = new(TenantResourceGroupRoles) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *TenantResourceGroupRoles) Init() *TenantResourceGroupRoles { + if pTypeDef.Roles == nil { + pTypeDef.Roles = make([]*TenantRoleAction, 0) + } + return pTypeDef +} + +type rawTenantResourceGroupRoles TenantResourceGroupRoles + +// +// UnmarshalJSON is defined for proper JSON decoding of a TenantResourceGroupRoles +// +func (pTypeDef *TenantResourceGroupRoles) UnmarshalJSON(b []byte) error { + var r rawTenantResourceGroupRoles + err := json.Unmarshal(b, &r) + if err == nil { + o := TenantResourceGroupRoles(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *TenantResourceGroupRoles) Validate() error { + if pTypeDef.Domain == "" { + return fmt.Errorf("TenantResourceGroupRoles.domain is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Domain) + if !val.Valid { + return fmt.Errorf("TenantResourceGroupRoles.domain does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Service == "" { + return fmt.Errorf("TenantResourceGroupRoles.service is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "SimpleName", pTypeDef.Service) + if !val.Valid { + return fmt.Errorf("TenantResourceGroupRoles.service does not contain a valid SimpleName (%v)", val.Error) + } + } + if pTypeDef.Tenant == "" { + return fmt.Errorf("TenantResourceGroupRoles.tenant is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Tenant) + if !val.Valid { + return fmt.Errorf("TenantResourceGroupRoles.tenant does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Roles == nil { + return fmt.Errorf("TenantResourceGroupRoles: Missing required field: roles") + } + if pTypeDef.ResourceGroup == "" { + return fmt.Errorf("TenantResourceGroupRoles.resourceGroup is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "EntityName", pTypeDef.ResourceGroup) + if !val.Valid { + return fmt.Errorf("TenantResourceGroupRoles.resourceGroup does not contain a valid EntityName (%v)", val.Error) + } + } + return nil +} + +// +// ProviderResourceGroupRoles - A representation of provider roles to be +// provisioned. +// +type ProviderResourceGroupRoles struct { + + // + // name of the provider domain + // + Domain DomainName `json:"domain"` + + // + // name of the provider service + // + Service SimpleName `json:"service"` + + // + // name of the tenant domain + // + Tenant DomainName `json:"tenant"` + + // + // the role/action pairs to provision + // + Roles []*TenantRoleAction `json:"roles"` + + // + // tenant resource group + // + ResourceGroup EntityName `json:"resourceGroup"` +} + +// +// NewProviderResourceGroupRoles - creates an initialized ProviderResourceGroupRoles instance, returns a pointer to it +// +func NewProviderResourceGroupRoles(init ...*ProviderResourceGroupRoles) *ProviderResourceGroupRoles { + var o *ProviderResourceGroupRoles + if len(init) == 1 { + o = init[0] + } else { + o = new(ProviderResourceGroupRoles) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *ProviderResourceGroupRoles) Init() *ProviderResourceGroupRoles { + if pTypeDef.Roles == nil { + pTypeDef.Roles = make([]*TenantRoleAction, 0) + } + return pTypeDef +} + +type rawProviderResourceGroupRoles ProviderResourceGroupRoles + +// +// UnmarshalJSON is defined for proper JSON decoding of a ProviderResourceGroupRoles +// +func (pTypeDef *ProviderResourceGroupRoles) UnmarshalJSON(b []byte) error { + var r rawProviderResourceGroupRoles + err := json.Unmarshal(b, &r) + if err == nil { + o := ProviderResourceGroupRoles(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *ProviderResourceGroupRoles) Validate() error { + if pTypeDef.Domain == "" { + return fmt.Errorf("ProviderResourceGroupRoles.domain is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Domain) + if !val.Valid { + return fmt.Errorf("ProviderResourceGroupRoles.domain does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Service == "" { + return fmt.Errorf("ProviderResourceGroupRoles.service is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "SimpleName", pTypeDef.Service) + if !val.Valid { + return fmt.Errorf("ProviderResourceGroupRoles.service does not contain a valid SimpleName (%v)", val.Error) + } + } + if pTypeDef.Tenant == "" { + return fmt.Errorf("ProviderResourceGroupRoles.tenant is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Tenant) + if !val.Valid { + return fmt.Errorf("ProviderResourceGroupRoles.tenant does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Roles == nil { + return fmt.Errorf("ProviderResourceGroupRoles: Missing required field: roles") + } + if pTypeDef.ResourceGroup == "" { + return fmt.Errorf("ProviderResourceGroupRoles.resourceGroup is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "EntityName", pTypeDef.ResourceGroup) + if !val.Valid { + return fmt.Errorf("ProviderResourceGroupRoles.resourceGroup does not contain a valid EntityName (%v)", val.Error) + } + } + return nil +} + +// +// Access - Access can be checked and returned as this resource. +// +type Access struct { + + // + // true (allowed) or false (denied) + // + Granted bool `json:"granted"` +} + +// +// NewAccess - creates an initialized Access instance, returns a pointer to it +// +func NewAccess(init ...*Access) *Access { + var o *Access + if len(init) == 1 { + o = init[0] + } else { + o = new(Access) + } + return o +} + +type rawAccess Access + +// +// UnmarshalJSON is defined for proper JSON decoding of a Access +// +func (pTypeDef *Access) UnmarshalJSON(b []byte) error { + var r rawAccess + err := json.Unmarshal(b, &r) + if err == nil { + o := Access(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Access) Validate() error { + return nil +} + +// +// ResourceAccess - +// +type ResourceAccess struct { + Principal EntityName `json:"principal"` + Assertions []*Assertion `json:"assertions"` +} + +// +// NewResourceAccess - creates an initialized ResourceAccess instance, returns a pointer to it +// +func NewResourceAccess(init ...*ResourceAccess) *ResourceAccess { + var o *ResourceAccess + if len(init) == 1 { + o = init[0] + } else { + o = new(ResourceAccess) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *ResourceAccess) Init() *ResourceAccess { + if pTypeDef.Assertions == nil { + pTypeDef.Assertions = make([]*Assertion, 0) + } + return pTypeDef +} + +type rawResourceAccess ResourceAccess + +// +// UnmarshalJSON is defined for proper JSON decoding of a ResourceAccess +// +func (pTypeDef *ResourceAccess) UnmarshalJSON(b []byte) error { + var r rawResourceAccess + err := json.Unmarshal(b, &r) + if err == nil { + o := ResourceAccess(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *ResourceAccess) Validate() error { + if pTypeDef.Principal == "" { + return fmt.Errorf("ResourceAccess.principal is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "EntityName", pTypeDef.Principal) + if !val.Valid { + return fmt.Errorf("ResourceAccess.principal does not contain a valid EntityName (%v)", val.Error) + } + } + if pTypeDef.Assertions == nil { + return fmt.Errorf("ResourceAccess: Missing required field: assertions") + } + return nil +} + +// +// ResourceAccessList - +// +type ResourceAccessList struct { + Resources []*ResourceAccess `json:"resources"` +} + +// +// NewResourceAccessList - creates an initialized ResourceAccessList instance, returns a pointer to it +// +func NewResourceAccessList(init ...*ResourceAccessList) *ResourceAccessList { + var o *ResourceAccessList + if len(init) == 1 { + o = init[0] + } else { + o = new(ResourceAccessList) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *ResourceAccessList) Init() *ResourceAccessList { + if pTypeDef.Resources == nil { + pTypeDef.Resources = make([]*ResourceAccess, 0) + } + return pTypeDef +} + +type rawResourceAccessList ResourceAccessList + +// +// UnmarshalJSON is defined for proper JSON decoding of a ResourceAccessList +// +func (pTypeDef *ResourceAccessList) UnmarshalJSON(b []byte) error { + var r rawResourceAccessList + err := json.Unmarshal(b, &r) + if err == nil { + o := ResourceAccessList(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *ResourceAccessList) Validate() error { + if pTypeDef.Resources == nil { + return fmt.Errorf("ResourceAccessList: Missing required field: resources") + } + return nil +} + +// +// DomainModified - Tuple of domain-name and modification time-stamps. This +// object is returned when the caller has requested list of domains modified +// since a specific timestamp. +// +type DomainModified struct { + + // + // name of the domain + // + Name DomainName `json:"name"` + + // + // last modified timestamp of the domain + // + Modified int64 `json:"modified"` +} + +// +// NewDomainModified - creates an initialized DomainModified instance, returns a pointer to it +// +func NewDomainModified(init ...*DomainModified) *DomainModified { + var o *DomainModified + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainModified) + } + return o +} + +type rawDomainModified DomainModified + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainModified +// +func (pTypeDef *DomainModified) UnmarshalJSON(b []byte) error { + var r rawDomainModified + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainModified(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainModified) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("DomainModified.name is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("DomainModified.name does not contain a valid DomainName (%v)", val.Error) + } + } + return nil +} + +// +// DomainModifiedList - A list of {domain, modified-timestamp} tuples. +// +type DomainModifiedList struct { + + // + // list of modified domains + // + NameModList []*DomainModified `json:"nameModList"` +} + +// +// NewDomainModifiedList - creates an initialized DomainModifiedList instance, returns a pointer to it +// +func NewDomainModifiedList(init ...*DomainModifiedList) *DomainModifiedList { + var o *DomainModifiedList + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainModifiedList) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *DomainModifiedList) Init() *DomainModifiedList { + if pTypeDef.NameModList == nil { + pTypeDef.NameModList = make([]*DomainModified, 0) + } + return pTypeDef +} + +type rawDomainModifiedList DomainModifiedList + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainModifiedList +// +func (pTypeDef *DomainModifiedList) UnmarshalJSON(b []byte) error { + var r rawDomainModifiedList + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainModifiedList(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainModifiedList) Validate() error { + if pTypeDef.NameModList == nil { + return fmt.Errorf("DomainModifiedList: Missing required field: nameModList") + } + return nil +} + +// +// DomainPolicies - We need to include the name of the domain in this struct +// since this data will be passed back to ZPU through ZTS so we need to sign not +// only the list of policies but also the corresponding domain name that the +// policies belong to. +// +type DomainPolicies struct { + + // + // name of the domain + // + Domain DomainName `json:"domain"` + + // + // list of policies defined in this server + // + Policies []*Policy `json:"policies"` +} + +// +// NewDomainPolicies - creates an initialized DomainPolicies instance, returns a pointer to it +// +func NewDomainPolicies(init ...*DomainPolicies) *DomainPolicies { + var o *DomainPolicies + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainPolicies) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *DomainPolicies) Init() *DomainPolicies { + if pTypeDef.Policies == nil { + pTypeDef.Policies = make([]*Policy, 0) + } + return pTypeDef +} + +type rawDomainPolicies DomainPolicies + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainPolicies +// +func (pTypeDef *DomainPolicies) UnmarshalJSON(b []byte) error { + var r rawDomainPolicies + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainPolicies(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainPolicies) Validate() error { + if pTypeDef.Domain == "" { + return fmt.Errorf("DomainPolicies.domain is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Domain) + if !val.Valid { + return fmt.Errorf("DomainPolicies.domain does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Policies == nil { + return fmt.Errorf("DomainPolicies: Missing required field: policies") + } + return nil +} + +// +// SignedPolicies - A signed bulk transfer of policies. The data is signed with +// server's private key. +// +type SignedPolicies struct { + + // + // list of policies defined in a domain + // + Contents *DomainPolicies `json:"contents"` + + // + // signature generated based on the domain policies object + // + Signature string `json:"signature"` + + // + // the identifier of the key used to generate the signature + // + KeyId string `json:"keyId"` +} + +// +// NewSignedPolicies - creates an initialized SignedPolicies instance, returns a pointer to it +// +func NewSignedPolicies(init ...*SignedPolicies) *SignedPolicies { + var o *SignedPolicies + if len(init) == 1 { + o = init[0] + } else { + o = new(SignedPolicies) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *SignedPolicies) Init() *SignedPolicies { + if pTypeDef.Contents == nil { + pTypeDef.Contents = NewDomainPolicies() + } + return pTypeDef +} + +type rawSignedPolicies SignedPolicies + +// +// UnmarshalJSON is defined for proper JSON decoding of a SignedPolicies +// +func (pTypeDef *SignedPolicies) UnmarshalJSON(b []byte) error { + var r rawSignedPolicies + err := json.Unmarshal(b, &r) + if err == nil { + o := SignedPolicies(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *SignedPolicies) Validate() error { + if pTypeDef.Contents == nil { + return fmt.Errorf("SignedPolicies: Missing required field: contents") + } + if pTypeDef.Signature == "" { + return fmt.Errorf("SignedPolicies.signature is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "String", pTypeDef.Signature) + if !val.Valid { + return fmt.Errorf("SignedPolicies.signature does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.KeyId == "" { + return fmt.Errorf("SignedPolicies.keyId is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "String", pTypeDef.KeyId) + if !val.Valid { + return fmt.Errorf("SignedPolicies.keyId does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// DomainData - A domain object that includes its roles, policies and services. +// +type DomainData struct { + + // + // name of the domain + // + Name DomainName `json:"name"` + + // + // associated cloud (i.e. aws) account id + // + Account string `json:"account,omitempty" rdl:"optional"` + + // + // associated product id + // + YpmId *int32 `json:"ypmId,omitempty" rdl:"optional"` + + // + // list of roles in the domain + // + Roles []*Role `json:"roles"` + + // + // list of policies in the domain signed with ZMS private key + // + Policies *SignedPolicies `json:"policies"` + + // + // list of services in the domain + // + Services []*ServiceIdentity `json:"services"` + + // + // list of entities in the domain + // + Entities []*Entity `json:"entities"` + + // + // last modification timestamp + // + Modified rdl.Timestamp `json:"modified"` +} + +// +// NewDomainData - creates an initialized DomainData instance, returns a pointer to it +// +func NewDomainData(init ...*DomainData) *DomainData { + var o *DomainData + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainData) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *DomainData) Init() *DomainData { + if pTypeDef.Roles == nil { + pTypeDef.Roles = make([]*Role, 0) + } + if pTypeDef.Policies == nil { + pTypeDef.Policies = NewSignedPolicies() + } + if pTypeDef.Services == nil { + pTypeDef.Services = make([]*ServiceIdentity, 0) + } + if pTypeDef.Entities == nil { + pTypeDef.Entities = make([]*Entity, 0) + } + return pTypeDef +} + +type rawDomainData DomainData + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainData +// +func (pTypeDef *DomainData) UnmarshalJSON(b []byte) error { + var r rawDomainData + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainData(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainData) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("DomainData.name is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("DomainData.name does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Roles == nil { + return fmt.Errorf("DomainData: Missing required field: roles") + } + if pTypeDef.Policies == nil { + return fmt.Errorf("DomainData: Missing required field: policies") + } + if pTypeDef.Services == nil { + return fmt.Errorf("DomainData: Missing required field: services") + } + if pTypeDef.Entities == nil { + return fmt.Errorf("DomainData: Missing required field: entities") + } + if pTypeDef.Modified.IsZero() { + return fmt.Errorf("DomainData: Missing required field: modified") + } + return nil +} + +// +// SignedDomain - A domain object signed with server's private key +// +type SignedDomain struct { + + // + // domain object with its roles, policies and services + // + Domain *DomainData `json:"domain"` + + // + // signature generated based on the domain object + // + Signature string `json:"signature"` + + // + // the identifier of the key used to generate the signature + // + KeyId string `json:"keyId"` +} + +// +// NewSignedDomain - creates an initialized SignedDomain instance, returns a pointer to it +// +func NewSignedDomain(init ...*SignedDomain) *SignedDomain { + var o *SignedDomain + if len(init) == 1 { + o = init[0] + } else { + o = new(SignedDomain) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *SignedDomain) Init() *SignedDomain { + if pTypeDef.Domain == nil { + pTypeDef.Domain = NewDomainData() + } + return pTypeDef +} + +type rawSignedDomain SignedDomain + +// +// UnmarshalJSON is defined for proper JSON decoding of a SignedDomain +// +func (pTypeDef *SignedDomain) UnmarshalJSON(b []byte) error { + var r rawSignedDomain + err := json.Unmarshal(b, &r) + if err == nil { + o := SignedDomain(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *SignedDomain) Validate() error { + if pTypeDef.Domain == nil { + return fmt.Errorf("SignedDomain: Missing required field: domain") + } + if pTypeDef.Signature == "" { + return fmt.Errorf("SignedDomain.signature is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "String", pTypeDef.Signature) + if !val.Valid { + return fmt.Errorf("SignedDomain.signature does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.KeyId == "" { + return fmt.Errorf("SignedDomain.keyId is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "String", pTypeDef.KeyId) + if !val.Valid { + return fmt.Errorf("SignedDomain.keyId does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// SignedDomains - A list of signed domain objects +// +type SignedDomains struct { + Domains []*SignedDomain `json:"domains"` +} + +// +// NewSignedDomains - creates an initialized SignedDomains instance, returns a pointer to it +// +func NewSignedDomains(init ...*SignedDomains) *SignedDomains { + var o *SignedDomains + if len(init) == 1 { + o = init[0] + } else { + o = new(SignedDomains) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *SignedDomains) Init() *SignedDomains { + if pTypeDef.Domains == nil { + pTypeDef.Domains = make([]*SignedDomain, 0) + } + return pTypeDef +} + +type rawSignedDomains SignedDomains + +// +// UnmarshalJSON is defined for proper JSON decoding of a SignedDomains +// +func (pTypeDef *SignedDomains) UnmarshalJSON(b []byte) error { + var r rawSignedDomains + err := json.Unmarshal(b, &r) + if err == nil { + o := SignedDomains(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *SignedDomains) Validate() error { + if pTypeDef.Domains == nil { + return fmt.Errorf("SignedDomains: Missing required field: domains") + } + return nil +} + +// +// UserToken - A user token generated based on user's credentials +// +type UserToken struct { + + // + // Signed user token identifying a specific authenticated user + // + Token SignedToken `json:"token"` +} + +// +// NewUserToken - creates an initialized UserToken instance, returns a pointer to it +// +func NewUserToken(init ...*UserToken) *UserToken { + var o *UserToken + if len(init) == 1 { + o = init[0] + } else { + o = new(UserToken) + } + return o +} + +type rawUserToken UserToken + +// +// UnmarshalJSON is defined for proper JSON decoding of a UserToken +// +func (pTypeDef *UserToken) UnmarshalJSON(b []byte) error { + var r rawUserToken + err := json.Unmarshal(b, &r) + if err == nil { + o := UserToken(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *UserToken) Validate() error { + if pTypeDef.Token == "" { + return fmt.Errorf("UserToken.token is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "SignedToken", pTypeDef.Token) + if !val.Valid { + return fmt.Errorf("UserToken.token does not contain a valid SignedToken (%v)", val.Error) + } + } + return nil +} + +// +// ServicePrincipal - A service principal object identifying a given service. +// +type ServicePrincipal struct { + + // + // name of the domain + // + Domain DomainName `json:"domain"` + + // + // name of the service + // + Service EntityName `json:"service"` + + // + // service's signed token + // + Token SignedToken `json:"token"` +} + +// +// NewServicePrincipal - creates an initialized ServicePrincipal instance, returns a pointer to it +// +func NewServicePrincipal(init ...*ServicePrincipal) *ServicePrincipal { + var o *ServicePrincipal + if len(init) == 1 { + o = init[0] + } else { + o = new(ServicePrincipal) + } + return o +} + +type rawServicePrincipal ServicePrincipal + +// +// UnmarshalJSON is defined for proper JSON decoding of a ServicePrincipal +// +func (pTypeDef *ServicePrincipal) UnmarshalJSON(b []byte) error { + var r rawServicePrincipal + err := json.Unmarshal(b, &r) + if err == nil { + o := ServicePrincipal(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *ServicePrincipal) Validate() error { + if pTypeDef.Domain == "" { + return fmt.Errorf("ServicePrincipal.domain is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "DomainName", pTypeDef.Domain) + if !val.Valid { + return fmt.Errorf("ServicePrincipal.domain does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Service == "" { + return fmt.Errorf("ServicePrincipal.service is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "EntityName", pTypeDef.Service) + if !val.Valid { + return fmt.Errorf("ServicePrincipal.service does not contain a valid EntityName (%v)", val.Error) + } + } + if pTypeDef.Token == "" { + return fmt.Errorf("ServicePrincipal.token is missing but is a required field") + } else { + val := rdl.Validate(ZMSSchema(), "SignedToken", pTypeDef.Token) + if !val.Valid { + return fmt.Errorf("ServicePrincipal.token does not contain a valid SignedToken (%v)", val.Error) + } + } + return nil +} diff --git a/clients/go/zms/zms_schema.go b/clients/go/zms/zms_schema.go new file mode 100644 index 00000000000..6779d012123 --- /dev/null +++ b/clients/go/zms/zms_schema.go @@ -0,0 +1,1233 @@ +// +// This file generated by rdl 1.4.8 +// + +package zms + +import ( + rdl "github.com/ardielle/ardielle-go/rdl" +) + +var schema *rdl.Schema + +func init() { + sb := rdl.NewSchemaBuilder("ZMS") + sb.Version(1) + sb.Namespace("com.yahoo.athenz.zms") + sb.Comment("Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. The Authorization Management Service (ZMS) Classes") + + tSimpleName := rdl.NewStringTypeBuilder("SimpleName") + tSimpleName.Comment("Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. Common name types used by several API definitions A simple identifier, an element of compound name.") + tSimpleName.Pattern("[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tSimpleName.Build()) + + tCompoundName := rdl.NewStringTypeBuilder("CompoundName") + tCompoundName.Comment("A compound name. Most names in this API are compound names.") + tCompoundName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tCompoundName.Build()) + + tDomainName := rdl.NewStringTypeBuilder("DomainName") + tDomainName.Comment("A domain name is the general qualifier prefix, as its uniqueness is managed.") + tDomainName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tDomainName.Build()) + + tEntityName := rdl.NewStringTypeBuilder("EntityName") + tEntityName.Comment("An entity name is a short form of a resource name, including only the domain and entity.") + tEntityName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tEntityName.Build()) + + tServiceName := rdl.NewStringTypeBuilder("ServiceName") + tServiceName.Comment("A service name will generally be a unique subdomain.") + tServiceName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tServiceName.Build()) + + tLocationName := rdl.NewStringTypeBuilder("LocationName") + tLocationName.Comment("A location name is not yet defined, but will be a dotted name like everything else.") + tLocationName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tLocationName.Build()) + + tActionName := rdl.NewStringTypeBuilder("ActionName") + tActionName.Comment("An action (operation) name.") + tActionName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tActionName.Build()) + + tResourceName := rdl.NewStringTypeBuilder("ResourceName") + tResourceName.Comment("A shorthand for a YRN with no service or location. The 'tail' of a YRN, just the domain:entity. Note that the EntityName part is optional, that is, a domain name followed by a colon is valid resource name.") + tResourceName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*(:([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?") + sb.AddType(tResourceName.Build()) + + tYRN := rdl.NewStringTypeBuilder("YRN") + tYRN.Comment("A full Yahoo Resource name (YRN).") + tYRN.Pattern("(yrn:(([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?:(([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?:)?([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*(:([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?") + sb.AddType(tYRN.Build()) + + tYBase64 := rdl.NewStringTypeBuilder("YBase64") + tYBase64.Comment("The Y-specific URL-safe Base64 variant.") + tYBase64.Pattern("[a-zA-Z0-9\\._-]+") + sb.AddType(tYBase64.Build()) + + tYEncoded := rdl.NewStringTypeBuilder("YEncoded") + tYEncoded.Comment("YEncoded includes ybase64 chars, as well as = and %. This can represent a user cookie and URL-encoded values.") + tYEncoded.Pattern("[a-zA-Z0-9\\._%=-]*") + sb.AddType(tYEncoded.Build()) + + tAuthorityName := rdl.NewStringTypeBuilder("AuthorityName") + tAuthorityName.Comment("Used as the prefix in a signed assertion. This uniquely identifies a signing authority. i.e. \"user\"") + tAuthorityName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tAuthorityName.Build()) + + tSignedToken := rdl.NewStringTypeBuilder("SignedToken") + tSignedToken.Comment("A signed assertion if identity. i.e. the user cookie value. This token will only make sense to the authority that generated it, so it is beneficial to have something in the value that is cheaply recognized to quickly reject if it belongs to another authority. In addition to the YEncoded set our token includes ; to separate components and , to separate roles and : for IPv6 addresses") + tSignedToken.Pattern("[a-zA-Z0-9\\._%=:;,-]*") + sb.AddType(tSignedToken.Build()) + + tDomain := rdl.NewStructTypeBuilder("Struct", "Domain") + tDomain.Comment("A domain is an independent partition of users, roles, and resources. Its name represents the definition of a namespace; the only way a new namespace can be created, from the top, is by creating Domains. Administration of a domain is governed by the parent domain (using reverse-DNS namespaces). The top level domains are governed by the special \"sys.auth\" domain.") + tDomain.Field("name", "DomainName", false, nil, "the common name to be referred to, the symbolic id. It is immutable") + tDomain.Field("modified", "Timestamp", true, nil, "the last modification timestamp of any object or attribute in this domain") + tDomain.Field("id", "UUID", true, nil, "unique identifier of the domain. generated on create, never reused") + tDomain.Field("description", "String", true, nil, "description of the domain") + tDomain.Field("org", "ResourceName", true, nil, "a reference to an Organization") + tDomain.Field("enabled", "Bool", true, true, "Future use only, currently not used") + tDomain.Field("auditEnabled", "Bool", true, false, "Flag indicates whether or not domain modifications should be logged for SOX+Auditing. If true, the auditRef parameter must be supplied(not empty) for any API defining it.") + tDomain.Field("account", "String", true, nil, "associated cloud (i.e. aws) account id") + tDomain.Field("ypmId", "Int32", true, nil, "associated product id") + sb.AddType(tDomain.Build()) + + tRoleList := rdl.NewStructTypeBuilder("Struct", "RoleList") + tRoleList.Comment("The representation for an enumeration of roles in the namespace, with pagination.") + tRoleList.ArrayField("names", "EntityName", false, "list of role names") + tRoleList.Field("next", "String", true, nil, "if the response is a paginated list, this attribute specifies the value to be used in the next role list request as the value for the skip query parameter.") + sb.AddType(tRoleList.Build()) + + tRoleAuditLog := rdl.NewStructTypeBuilder("Struct", "RoleAuditLog") + tRoleAuditLog.Comment("An audit log entry for role membership change.") + tRoleAuditLog.Field("member", "ResourceName", false, nil, "name of the role member") + tRoleAuditLog.Field("admin", "ResourceName", false, nil, "name of the principal executing the change") + tRoleAuditLog.Field("created", "Timestamp", false, nil, "timestamp of the entry") + tRoleAuditLog.Field("action", "String", false, nil, "log action - either add or delete") + tRoleAuditLog.Field("auditRef", "String", true, nil, "audit reference string for the change as supplied by admin") + sb.AddType(tRoleAuditLog.Build()) + + tRole := rdl.NewStructTypeBuilder("Struct", "Role") + tRole.Comment("The representation for a Role with set of members.") + tRole.Field("name", "ResourceName", false, nil, "name of the role") + tRole.Field("modified", "Timestamp", true, nil, "last modification timestamp of the role") + tRole.ArrayField("members", "ResourceName", true, "an explicit list of members. Might be empty or null, if trust is set") + tRole.Field("trust", "DomainName", true, nil, "a trusted domain to delegate membership decisions to") + tRole.ArrayField("auditLog", "RoleAuditLog", true, "an audit log for role membership changes") + sb.AddType(tRole.Build()) + + tRoles := rdl.NewStructTypeBuilder("Struct", "Roles") + tRoles.Comment("The representation for a list of roles with full details") + tRoles.ArrayField("list", "Role", false, "list of role objects") + sb.AddType(tRoles.Build()) + + tMembership := rdl.NewStructTypeBuilder("Struct", "Membership") + tMembership.Comment("The representation for a role membership.") + tMembership.Field("memberName", "ResourceName", false, nil, "name of the member") + tMembership.Field("isMember", "Bool", true, true, "flag to indicate whether or the user is a member or not") + tMembership.Field("roleName", "ResourceName", true, nil, "name of the role") + sb.AddType(tMembership.Build()) + + tDefaultAdmins := rdl.NewStructTypeBuilder("Struct", "DefaultAdmins") + tDefaultAdmins.Comment("The list of domain administrators.") + tDefaultAdmins.ArrayField("admins", "ResourceName", false, "list of domain administrators") + sb.AddType(tDefaultAdmins.Build()) + + tAssertionEffect := rdl.NewEnumTypeBuilder("Enum", "AssertionEffect") + tAssertionEffect.Comment("Every assertion can have the effect of ALLOW or DENY.") + tAssertionEffect.Element("ALLOW", "Every assertion can have the effect of ALLOW or DENY.") + tAssertionEffect.Element("DENY", "") + sb.AddType(tAssertionEffect.Build()) + + tAssertion := rdl.NewStructTypeBuilder("Struct", "Assertion") + tAssertion.Comment("A representation for the encapsulation of an action to be performed on a resource by a principal.") + tAssertion.Field("role", "String", false, nil, "the subject of the assertion - a role") + tAssertion.Field("resource", "String", false, nil, "the object of the assertion. Must be in the local namespace. Can contain wildcards") + tAssertion.Field("action", "String", false, nil, "the predicate of the assertion. Can contain wildcards") + tAssertion.Field("effect", "AssertionEffect", true, ALLOW, "the effect of the assertion in the policy language") + tAssertion.Field("id", "Int64", true, nil, "assertion id - auto generated by server. Not required during put operations.") + sb.AddType(tAssertion.Build()) + + tPolicy := rdl.NewStructTypeBuilder("Struct", "Policy") + tPolicy.Comment("The representation for a Policy with set of assertions.") + tPolicy.Field("name", "ResourceName", false, nil, "name of the policy") + tPolicy.Field("modified", "Timestamp", true, nil, "last modification timestamp of this policy") + tPolicy.ArrayField("assertions", "Assertion", false, "list of defined assertions for this policy") + sb.AddType(tPolicy.Build()) + + tPolicies := rdl.NewStructTypeBuilder("Struct", "Policies") + tPolicies.Comment("The representation of list of policy objects") + tPolicies.ArrayField("list", "Policy", false, "list of policy objects") + sb.AddType(tPolicies.Build()) + + tTemplate := rdl.NewStructTypeBuilder("Struct", "Template") + tTemplate.Comment("Solution Template object defined on the server") + tTemplate.ArrayField("roles", "Role", false, "list of roles in the template") + tTemplate.ArrayField("policies", "Policy", false, "list of policies defined in this template") + sb.AddType(tTemplate.Build()) + + tTemplateList := rdl.NewStructTypeBuilder("Struct", "TemplateList") + tTemplateList.Comment("List of template names that is the base struct for server and domain templates") + tTemplateList.ArrayField("templateNames", "SimpleName", false, "list of template names") + sb.AddType(tTemplateList.Build()) + + tDomainTemplate := rdl.NewStructTypeBuilder("TemplateList", "DomainTemplate") + tDomainTemplate.Comment("solution template(s) to be applied to a domain") + sb.AddType(tDomainTemplate.Build()) + + tDomainTemplateList := rdl.NewStructTypeBuilder("TemplateList", "DomainTemplateList") + tDomainTemplateList.Comment("List of solution templates to be applied to a domain") + sb.AddType(tDomainTemplateList.Build()) + + tServerTemplateList := rdl.NewStructTypeBuilder("TemplateList", "ServerTemplateList") + tServerTemplateList.Comment("List of solution templates available in the server") + sb.AddType(tServerTemplateList.Build()) + + tDomainList := rdl.NewStructTypeBuilder("Struct", "DomainList") + tDomainList.Comment("A paginated list of domains.") + tDomainList.ArrayField("names", "DomainName", false, "list of domain names") + tDomainList.Field("next", "String", true, nil, "if the response is a paginated list, this attribute specifies the value to be used in the next domain list request as the value for the skip query parameter.") + sb.AddType(tDomainList.Build()) + + tDomainMeta := rdl.NewStructTypeBuilder("Struct", "DomainMeta") + tDomainMeta.Comment("Set of metadata attributes that all domains may have and can be changed.") + tDomainMeta.Field("description", "String", true, nil, "a description of the domain") + tDomainMeta.Field("org", "ResourceName", true, nil, "a reference to an Organization. (i.e. org:media)") + tDomainMeta.Field("enabled", "Bool", true, true, "Future use only, currently not used") + tDomainMeta.Field("auditEnabled", "Bool", true, false, "Flag indicates whether or not domain modifications should be logged for SOX+Auditing. If true, the auditRef parameter must be supplied(not empty) for any API defining it.") + tDomainMeta.Field("account", "String", true, nil, "associated cloud (i.e. aws) account id") + tDomainMeta.Field("ypmId", "Int32", true, nil, "associated product id") + sb.AddType(tDomainMeta.Build()) + + tTopLevelDomain := rdl.NewStructTypeBuilder("DomainMeta", "TopLevelDomain") + tTopLevelDomain.Comment("Top Level Domain object. The required attributes include the name of the domain and list of domain administrators.") + tTopLevelDomain.Field("name", "SimpleName", false, nil, "name of the domain") + tTopLevelDomain.ArrayField("adminUsers", "ResourceName", false, "list of domain administrators") + tTopLevelDomain.Field("templates", "DomainTemplateList", true, nil, "list of solution template names") + sb.AddType(tTopLevelDomain.Build()) + + tSubDomain := rdl.NewStructTypeBuilder("TopLevelDomain", "SubDomain") + tSubDomain.Comment("A Subdomain is a TopLevelDomain, except it has a parent.") + tSubDomain.Field("parent", "DomainName", false, nil, "name of the parent domain") + sb.AddType(tSubDomain.Build()) + + tUserDomain := rdl.NewStructTypeBuilder("DomainMeta", "UserDomain") + tUserDomain.Comment("A UserDomain is the user's own top level domain in user - e.g. user.hga") + tUserDomain.Field("name", "SimpleName", false, nil, "user id which will be the domain name") + tUserDomain.Field("templates", "DomainTemplateList", true, nil, "list of solution template names") + sb.AddType(tUserDomain.Build()) + + tDanglingPolicy := rdl.NewStructTypeBuilder("Struct", "DanglingPolicy") + tDanglingPolicy.Comment("A dangling policy where the assertion is referencing a role name that doesn't exist in the domain") + tDanglingPolicy.Field("policyName", "EntityName", false, nil, "") + tDanglingPolicy.Field("roleName", "EntityName", false, nil, "") + sb.AddType(tDanglingPolicy.Build()) + + tDomainDataCheck := rdl.NewStructTypeBuilder("Struct", "DomainDataCheck") + tDomainDataCheck.Comment("Domain data object representing the results of a check operation looking for dangling roles, policies and trust relationships that are set either on tenant or provider side only") + tDomainDataCheck.ArrayField("danglingRoles", "EntityName", true, "Names of roles not specified in any assertion. Might be empty or null if no dangling roles.") + tDomainDataCheck.ArrayField("danglingPolicies", "DanglingPolicy", true, "Policy+role tuples where role doesnt exist. Might be empty or null if no dangling policies.") + tDomainDataCheck.Field("policyCount", "Int32", false, nil, "total number of policies") + tDomainDataCheck.Field("assertionCount", "Int32", false, nil, "total number of assertions") + tDomainDataCheck.Field("roleWildCardCount", "Int32", false, nil, "total number of assertions containing roles as wildcards") + tDomainDataCheck.ArrayField("providersWithoutTrust", "ServiceName", true, "Service names (domain.service) that dont contain trust role if this is a tenant domain. Might be empty or null, if not a tenant or if all providers support this tenant.") + tDomainDataCheck.ArrayField("tenantsWithoutAssumeRole", "DomainName", true, "Names of Tenant domains that dont contain assume role assertions if this is a provider domain. Might be empty or null, if not a provider or if all tenants support use this provider.") + sb.AddType(tDomainDataCheck.Build()) + + tEntity := rdl.NewStructTypeBuilder("Struct", "Entity") + tEntity.Comment("An entity is a name and a structured value. some entity names/prefixes are reserved (i.e. \"role\", \"policy\", \"meta\", \"domain\", \"service\")") + tEntity.Field("name", "EntityName", false, nil, "name of the entity object") + tEntity.Field("value", "Struct", false, nil, "value of the entity") + sb.AddType(tEntity.Build()) + + tEntityList := rdl.NewStructTypeBuilder("Struct", "EntityList") + tEntityList.Comment("The representation for an enumeration of entities in the namespace") + tEntityList.ArrayField("names", "EntityName", false, "list of entity names") + sb.AddType(tEntityList.Build()) + + tPolicyList := rdl.NewStructTypeBuilder("Struct", "PolicyList") + tPolicyList.Comment("The representation for an enumeration of policies in the namespace, with pagination.") + tPolicyList.ArrayField("names", "EntityName", false, "list of policy names") + tPolicyList.Field("next", "String", true, nil, "if the response is a paginated list, this attribute specifies the value to be used in the next policy list request as the value for the skip query parameter.") + sb.AddType(tPolicyList.Build()) + + tPublicKeyEntry := rdl.NewStructTypeBuilder("Struct", "PublicKeyEntry") + tPublicKeyEntry.Comment("The representation of the public key in a service identity object.") + tPublicKeyEntry.Field("key", "String", false, nil, "the public key for the service") + tPublicKeyEntry.Field("id", "String", false, nil, "the key identifier (version or zone name)") + sb.AddType(tPublicKeyEntry.Build()) + + tServiceIdentity := rdl.NewStructTypeBuilder("Struct", "ServiceIdentity") + tServiceIdentity.Comment("The representation of the service identity object.") + tServiceIdentity.Field("name", "ServiceName", false, nil, "the full name of the service, i.e. \"sports.storage\"") + tServiceIdentity.ArrayField("publicKeys", "PublicKeyEntry", true, "array of public keys for key rotation") + tServiceIdentity.Field("providerEndpoint", "String", true, nil, "if present, then this service can provision tenants via this endpoint.") + tServiceIdentity.Field("modified", "Timestamp", true, nil, "the timestamp when this entry was last modified") + tServiceIdentity.Field("executable", "String", true, nil, "the path of the executable that runs the service") + tServiceIdentity.ArrayField("hosts", "String", true, "list of host names that this service can run on") + tServiceIdentity.Field("user", "String", true, nil, "local (unix) user name this service can run as") + tServiceIdentity.Field("group", "String", true, nil, "local (unix) group name this service can run as") + sb.AddType(tServiceIdentity.Build()) + + tServiceIdentities := rdl.NewStructTypeBuilder("Struct", "ServiceIdentities") + tServiceIdentities.Comment("The representation of list of services") + tServiceIdentities.ArrayField("list", "ServiceIdentity", false, "list of services") + sb.AddType(tServiceIdentities.Build()) + + tServiceIdentityList := rdl.NewStructTypeBuilder("Struct", "ServiceIdentityList") + tServiceIdentityList.Comment("The representation for an enumeration of services in the namespace, with pagination.") + tServiceIdentityList.ArrayField("names", "EntityName", false, "list of service names") + tServiceIdentityList.Field("next", "String", true, nil, "if the response is a paginated list, this attribute specifies the value to be used in the next service list request as the value for the skip query parameter.") + sb.AddType(tServiceIdentityList.Build()) + + tTenancy := rdl.NewStructTypeBuilder("Struct", "Tenancy") + tTenancy.Comment("A representation of tenant.") + tTenancy.Field("domain", "DomainName", false, nil, "the domain that is to get a tenancy") + tTenancy.Field("service", "ServiceName", false, nil, "the provider service on which the tenancy is to reside") + tTenancy.ArrayField("resourceGroups", "EntityName", true, "registered resource groups for this tenant") + sb.AddType(tTenancy.Build()) + + tTenancyResourceGroup := rdl.NewStructTypeBuilder("Struct", "TenancyResourceGroup") + tTenancyResourceGroup.Field("domain", "DomainName", false, nil, "the domain that is to get a tenancy") + tTenancyResourceGroup.Field("service", "ServiceName", false, nil, "the provider service on which the tenancy is to reside") + tTenancyResourceGroup.Field("resourceGroup", "EntityName", false, nil, "registered resource group for this tenant") + sb.AddType(tTenancyResourceGroup.Build()) + + tTenantRoleAction := rdl.NewStructTypeBuilder("Struct", "TenantRoleAction") + tTenantRoleAction.Comment("A representation of tenant role action.") + tTenantRoleAction.Field("role", "SimpleName", false, nil, "name of the role") + tTenantRoleAction.Field("action", "String", false, nil, "action value for the generated policy assertion") + sb.AddType(tTenantRoleAction.Build()) + + tTenantRoles := rdl.NewStructTypeBuilder("Struct", "TenantRoles") + tTenantRoles.Comment("A representation of tenant roles to be provisioned.") + tTenantRoles.Field("domain", "DomainName", false, nil, "name of the provider domain") + tTenantRoles.Field("service", "SimpleName", false, nil, "name of the provider service") + tTenantRoles.Field("tenant", "DomainName", false, nil, "name of the tenant domain") + tTenantRoles.ArrayField("roles", "TenantRoleAction", false, "the role/action pairs to provision") + sb.AddType(tTenantRoles.Build()) + + tTenantResourceGroupRoles := rdl.NewStructTypeBuilder("Struct", "TenantResourceGroupRoles") + tTenantResourceGroupRoles.Comment("A representation of tenant roles for resource groups to be provisioned.") + tTenantResourceGroupRoles.Field("domain", "DomainName", false, nil, "name of the provider domain") + tTenantResourceGroupRoles.Field("service", "SimpleName", false, nil, "name of the provider service") + tTenantResourceGroupRoles.Field("tenant", "DomainName", false, nil, "name of the tenant domain") + tTenantResourceGroupRoles.ArrayField("roles", "TenantRoleAction", false, "the role/action pairs to provision") + tTenantResourceGroupRoles.Field("resourceGroup", "EntityName", false, nil, "tenant resource group") + sb.AddType(tTenantResourceGroupRoles.Build()) + + tProviderResourceGroupRoles := rdl.NewStructTypeBuilder("Struct", "ProviderResourceGroupRoles") + tProviderResourceGroupRoles.Comment("A representation of provider roles to be provisioned.") + tProviderResourceGroupRoles.Field("domain", "DomainName", false, nil, "name of the provider domain") + tProviderResourceGroupRoles.Field("service", "SimpleName", false, nil, "name of the provider service") + tProviderResourceGroupRoles.Field("tenant", "DomainName", false, nil, "name of the tenant domain") + tProviderResourceGroupRoles.ArrayField("roles", "TenantRoleAction", false, "the role/action pairs to provision") + tProviderResourceGroupRoles.Field("resourceGroup", "EntityName", false, nil, "tenant resource group") + sb.AddType(tProviderResourceGroupRoles.Build()) + + tAccess := rdl.NewStructTypeBuilder("Struct", "Access") + tAccess.Comment("Access can be checked and returned as this resource.") + tAccess.Field("granted", "Bool", false, nil, "true (allowed) or false (denied)") + sb.AddType(tAccess.Build()) + + tResourceAccess := rdl.NewStructTypeBuilder("Struct", "ResourceAccess") + tResourceAccess.Field("principal", "EntityName", false, nil, "") + tResourceAccess.ArrayField("assertions", "Assertion", false, "") + sb.AddType(tResourceAccess.Build()) + + tResourceAccessList := rdl.NewStructTypeBuilder("Struct", "ResourceAccessList") + tResourceAccessList.ArrayField("resources", "ResourceAccess", false, "") + sb.AddType(tResourceAccessList.Build()) + + tDomainModified := rdl.NewStructTypeBuilder("Struct", "DomainModified") + tDomainModified.Comment("Tuple of domain-name and modification time-stamps. This object is returned when the caller has requested list of domains modified since a specific timestamp.") + tDomainModified.Field("name", "DomainName", false, nil, "name of the domain") + tDomainModified.Field("modified", "Int64", false, nil, "last modified timestamp of the domain") + sb.AddType(tDomainModified.Build()) + + tDomainModifiedList := rdl.NewStructTypeBuilder("Struct", "DomainModifiedList") + tDomainModifiedList.Comment("A list of {domain, modified-timestamp} tuples.") + tDomainModifiedList.ArrayField("nameModList", "DomainModified", false, "list of modified domains") + sb.AddType(tDomainModifiedList.Build()) + + tDomainPolicies := rdl.NewStructTypeBuilder("Struct", "DomainPolicies") + tDomainPolicies.Comment("We need to include the name of the domain in this struct since this data will be passed back to ZPU through ZTS so we need to sign not only the list of policies but also the corresponding domain name that the policies belong to.") + tDomainPolicies.Field("domain", "DomainName", false, nil, "name of the domain") + tDomainPolicies.ArrayField("policies", "Policy", false, "list of policies defined in this server") + sb.AddType(tDomainPolicies.Build()) + + tSignedPolicies := rdl.NewStructTypeBuilder("Struct", "SignedPolicies") + tSignedPolicies.Comment("A signed bulk transfer of policies. The data is signed with server's private key.") + tSignedPolicies.Field("contents", "DomainPolicies", false, nil, "list of policies defined in a domain") + tSignedPolicies.Field("signature", "String", false, nil, "signature generated based on the domain policies object") + tSignedPolicies.Field("keyId", "String", false, nil, "the identifier of the key used to generate the signature") + sb.AddType(tSignedPolicies.Build()) + + tDomainData := rdl.NewStructTypeBuilder("Struct", "DomainData") + tDomainData.Comment("A domain object that includes its roles, policies and services.") + tDomainData.Field("name", "DomainName", false, nil, "name of the domain") + tDomainData.Field("account", "String", true, nil, "associated cloud (i.e. aws) account id") + tDomainData.Field("ypmId", "Int32", true, nil, "associated product id") + tDomainData.ArrayField("roles", "Role", false, "list of roles in the domain") + tDomainData.Field("policies", "SignedPolicies", false, nil, "list of policies in the domain signed with ZMS private key") + tDomainData.ArrayField("services", "ServiceIdentity", false, "list of services in the domain") + tDomainData.ArrayField("entities", "Entity", false, "list of entities in the domain") + tDomainData.Field("modified", "Timestamp", false, nil, "last modification timestamp") + sb.AddType(tDomainData.Build()) + + tSignedDomain := rdl.NewStructTypeBuilder("Struct", "SignedDomain") + tSignedDomain.Comment("A domain object signed with server's private key") + tSignedDomain.Field("domain", "DomainData", false, nil, "domain object with its roles, policies and services") + tSignedDomain.Field("signature", "String", false, nil, "signature generated based on the domain object") + tSignedDomain.Field("keyId", "String", false, nil, "the identifier of the key used to generate the signature") + sb.AddType(tSignedDomain.Build()) + + tSignedDomains := rdl.NewStructTypeBuilder("Struct", "SignedDomains") + tSignedDomains.Comment("A list of signed domain objects") + tSignedDomains.ArrayField("domains", "SignedDomain", false, "") + sb.AddType(tSignedDomains.Build()) + + tUserToken := rdl.NewStructTypeBuilder("Struct", "UserToken") + tUserToken.Comment("A user token generated based on user's credentials") + tUserToken.Field("token", "SignedToken", false, nil, "Signed user token identifying a specific authenticated user") + sb.AddType(tUserToken.Build()) + + tServicePrincipal := rdl.NewStructTypeBuilder("Struct", "ServicePrincipal") + tServicePrincipal.Comment("A service principal object identifying a given service.") + tServicePrincipal.Field("domain", "DomainName", false, nil, "name of the domain") + tServicePrincipal.Field("service", "EntityName", false, nil, "name of the service") + tServicePrincipal.Field("token", "SignedToken", false, nil, "service's signed token") + sb.AddType(tServicePrincipal.Build()) + + rGetDomain := rdl.NewResourceBuilder("Domain", "GET", "/domain/{domain}") + rGetDomain.Comment("Get info for the specified domain, by name. This request only returns the configured domain attributes and not any domain objects like roles, policies or service identities.") + rGetDomain.Input("domain", "DomainName", true, "", "", false, nil, "name of the domain") + rGetDomain.Auth("", "", true, "") + rGetDomain.Exception("BAD_REQUEST", "ResourceError", "") + rGetDomain.Exception("FORBIDDEN", "ResourceError", "") + rGetDomain.Exception("NOT_FOUND", "ResourceError", "") + rGetDomain.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetDomain.Build()) + + rGetDomainList := rdl.NewResourceBuilder("DomainList", "GET", "/domain") + rGetDomainList.Comment("Enumerate domains. Can be filtered by prefix and depth, and paginated. This operation can be expensive, as it may span multiple domains.") + rGetDomainList.Input("limit", "Int32", false, "limit", "", true, nil, "restrict the number of results in this call") + rGetDomainList.Input("skip", "String", false, "skip", "", true, nil, "restrict the set to those after the specified \"next\" token returned from a previous call") + rGetDomainList.Input("prefix", "String", false, "prefix", "", true, nil, "restrict to names that start with the prefix") + rGetDomainList.Input("depth", "Int32", false, "depth", "", true, nil, "restrict the depth of the name, specifying the number of '.' characters that can appear") + rGetDomainList.Input("account", "String", false, "account", "", true, nil, "restrict to domain names that have specified account name") + rGetDomainList.Input("productId", "Int32", false, "ypmid", "", true, nil, "restrict the domain names that have specified product id") + rGetDomainList.Input("roleMember", "ResourceName", false, "member", "", true, nil, "restrict the domain names where the specified user is in a role - see roleName") + rGetDomainList.Input("roleName", "ResourceName", false, "role", "", true, nil, "restrict the domain names where the specified user is in this role - see roleMember") + rGetDomainList.Input("modifiedSince", "String", false, "", "If-Modified-Since", false, nil, "This header specifies to the server to return any domains modified since this HTTP date") + rGetDomainList.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetDomainList.Build()) + + rPostTopLevelDomain := rdl.NewResourceBuilder("Domain", "POST", "/domain") + rPostTopLevelDomain.Comment("Create a new top level domain. This is a privileged action for the \"sys.auth\" administrators.") + rPostTopLevelDomain.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPostTopLevelDomain.Input("detail", "TopLevelDomain", false, "", "", false, nil, "TopLevelDomain object to be created") + rPostTopLevelDomain.Auth("create", "sys.auth:domain", false, "") + rPostTopLevelDomain.Exception("BAD_REQUEST", "ResourceError", "") + rPostTopLevelDomain.Exception("FORBIDDEN", "ResourceError", "") + rPostTopLevelDomain.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPostTopLevelDomain.Build()) + + rPostSubDomain := rdl.NewResourceBuilder("Domain", "POST", "/subdomain/{parent}") + rPostSubDomain.Comment("Create a new subdomain. The domain administrators of the {parent} domain have the privilege to create subdomains.") + rPostSubDomain.Input("parent", "DomainName", true, "", "", false, nil, "name of the parent domain") + rPostSubDomain.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPostSubDomain.Input("detail", "SubDomain", false, "", "", false, nil, "Subdomain object to be created") + rPostSubDomain.Auth("create", "{parent}:domain", false, "") + rPostSubDomain.Exception("BAD_REQUEST", "ResourceError", "") + rPostSubDomain.Exception("FORBIDDEN", "ResourceError", "") + rPostSubDomain.Exception("NOT_FOUND", "ResourceError", "") + rPostSubDomain.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPostSubDomain.Build()) + + rPostUserDomain := rdl.NewResourceBuilder("Domain", "POST", "/userdomain/{name}") + rPostUserDomain.Comment("Create a new user domain. The user domain will be created in the user top level domain and the user himself will be set as the administrator for this domain.") + rPostUserDomain.Input("name", "SimpleName", true, "", "", false, nil, "name of the domain which will be the user id") + rPostUserDomain.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPostUserDomain.Input("detail", "UserDomain", false, "", "", false, nil, "UserDomain object to be created") + rPostUserDomain.Auth("create", "user.{name}:domain", false, "") + rPostUserDomain.Exception("BAD_REQUEST", "ResourceError", "") + rPostUserDomain.Exception("FORBIDDEN", "ResourceError", "") + rPostUserDomain.Exception("NOT_FOUND", "ResourceError", "") + rPostUserDomain.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPostUserDomain.Build()) + + rDeleteTopLevelDomain := rdl.NewResourceBuilder("TopLevelDomain", "DELETE", "/domain/{name}") + rDeleteTopLevelDomain.Comment("Delete the specified domain. This is a privileged action for the \"sys.auth\" administrators. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeleteTopLevelDomain.Input("name", "DomainName", true, "", "", false, nil, "name of the domain to be deleted") + rDeleteTopLevelDomain.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteTopLevelDomain.Auth("delete", "sys.auth:domain", false, "") + rDeleteTopLevelDomain.Expected("NO_CONTENT") + rDeleteTopLevelDomain.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteTopLevelDomain.Exception("FORBIDDEN", "ResourceError", "") + rDeleteTopLevelDomain.Exception("NOT_FOUND", "ResourceError", "") + rDeleteTopLevelDomain.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteTopLevelDomain.Build()) + + rDeleteSubDomain := rdl.NewResourceBuilder("SubDomain", "DELETE", "/subdomain/{parent}/{name}") + rDeleteSubDomain.Comment("Delete the specified subdomain. Caller must have domain delete permissions in parent. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeleteSubDomain.Input("parent", "DomainName", true, "", "", false, nil, "name of the parent domain") + rDeleteSubDomain.Input("name", "DomainName", true, "", "", false, nil, "name of the subdomain to be deleted") + rDeleteSubDomain.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteSubDomain.Auth("delete", "{parent}:domain", false, "") + rDeleteSubDomain.Expected("NO_CONTENT") + rDeleteSubDomain.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteSubDomain.Exception("FORBIDDEN", "ResourceError", "") + rDeleteSubDomain.Exception("NOT_FOUND", "ResourceError", "") + rDeleteSubDomain.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteSubDomain.Build()) + + rDeleteUserDomain := rdl.NewResourceBuilder("UserDomain", "DELETE", "/userdomain/{name}") + rDeleteUserDomain.Comment("Delete the specified userdomain. Caller must have domain delete permissions in the domain. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeleteUserDomain.Input("name", "SimpleName", true, "", "", false, nil, "name of the domain to be deleted which will be the user id") + rDeleteUserDomain.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteUserDomain.Auth("delete", "user.{name}:domain", false, "") + rDeleteUserDomain.Expected("NO_CONTENT") + rDeleteUserDomain.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteUserDomain.Exception("FORBIDDEN", "ResourceError", "") + rDeleteUserDomain.Exception("NOT_FOUND", "ResourceError", "") + rDeleteUserDomain.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteUserDomain.Build()) + + rPutDomainMeta := rdl.NewResourceBuilder("Domain", "PUT", "/domain/{name}/meta") + rPutDomainMeta.Comment("Update the specified top level domain metadata. Note that entities in the domain are not affected. Caller must have update privileges on the domain itself.") + rPutDomainMeta.Input("name", "DomainName", true, "", "", false, nil, "name of the domain to be updated") + rPutDomainMeta.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutDomainMeta.Input("detail", "DomainMeta", false, "", "", false, nil, "DomainMeta object with updated attribute values") + rPutDomainMeta.Auth("update", "{name}:", false, "") + rPutDomainMeta.Expected("NO_CONTENT") + rPutDomainMeta.Exception("BAD_REQUEST", "ResourceError", "") + rPutDomainMeta.Exception("CONFLICT", "ResourceError", "") + rPutDomainMeta.Exception("FORBIDDEN", "ResourceError", "") + rPutDomainMeta.Exception("NOT_FOUND", "ResourceError", "") + rPutDomainMeta.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutDomainMeta.Build()) + + rPutDomainTemplate := rdl.NewResourceBuilder("DomainTemplate", "PUT", "/domain/{name}/template") + rPutDomainTemplate.Comment("Update the given domain by applying the roles and policies defined in the specified solution template(s). Caller must have UPDATE privileges on the domain itself.") + rPutDomainTemplate.Input("name", "DomainName", true, "", "", false, nil, "name of the domain to be updated") + rPutDomainTemplate.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutDomainTemplate.Input("template", "DomainTemplate", false, "", "", false, nil, "DomainTemplate object with solution template name(s)") + rPutDomainTemplate.Auth("update", "{name}:", false, "") + rPutDomainTemplate.Expected("NO_CONTENT") + rPutDomainTemplate.Exception("BAD_REQUEST", "ResourceError", "") + rPutDomainTemplate.Exception("CONFLICT", "ResourceError", "") + rPutDomainTemplate.Exception("FORBIDDEN", "ResourceError", "") + rPutDomainTemplate.Exception("NOT_FOUND", "ResourceError", "") + rPutDomainTemplate.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutDomainTemplate.Build()) + + rGetDomainTemplateList := rdl.NewResourceBuilder("DomainTemplateList", "GET", "/domain/{name}/template") + rGetDomainTemplateList.Comment("Get the list of solution templates applied to a domain") + rGetDomainTemplateList.Input("name", "DomainName", true, "", "", false, nil, "name of the domain") + rGetDomainTemplateList.Auth("", "", true, "") + rGetDomainTemplateList.Exception("BAD_REQUEST", "ResourceError", "") + rGetDomainTemplateList.Exception("NOT_FOUND", "ResourceError", "") + rGetDomainTemplateList.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetDomainTemplateList.Build()) + + rDeleteDomainTemplate := rdl.NewResourceBuilder("DomainTemplate", "DELETE", "/domain/{name}/template/{template}") + rDeleteDomainTemplate.Comment("Update the given domain by deleting the specified template from the domain template list. Cycles through the roles and policies defined in the template and deletes them. Caller must have delete privileges on the domain itself.") + rDeleteDomainTemplate.Input("name", "DomainName", true, "", "", false, nil, "name of the domain to be updated") + rDeleteDomainTemplate.Input("template", "SimpleName", true, "", "", false, nil, "name of the solution template") + rDeleteDomainTemplate.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteDomainTemplate.Auth("delete", "{name}:", false, "") + rDeleteDomainTemplate.Expected("NO_CONTENT") + rDeleteDomainTemplate.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteDomainTemplate.Exception("CONFLICT", "ResourceError", "") + rDeleteDomainTemplate.Exception("FORBIDDEN", "ResourceError", "") + rDeleteDomainTemplate.Exception("NOT_FOUND", "ResourceError", "") + rDeleteDomainTemplate.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteDomainTemplate.Build()) + + rGetDomainDataCheck := rdl.NewResourceBuilder("DomainDataCheck", "GET", "/domain/{domainName}/check") + rGetDomainDataCheck.Comment("Carry out data check operation for the specified domain.") + rGetDomainDataCheck.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetDomainDataCheck.Auth("", "", true, "") + rGetDomainDataCheck.Exception("FORBIDDEN", "ResourceError", "") + rGetDomainDataCheck.Exception("NOT_FOUND", "ResourceError", "") + rGetDomainDataCheck.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetDomainDataCheck.Build()) + + rPutEntity := rdl.NewResourceBuilder("Entity", "PUT", "/domain/{domainName}/entity/{entityName}") + rPutEntity.Comment("Put an entity into the domain.") + rPutEntity.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rPutEntity.Input("entityName", "EntityName", true, "", "", false, nil, "name of entity") + rPutEntity.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutEntity.Input("entity", "Entity", false, "", "", false, nil, "Entity object to be added to the domain") + rPutEntity.Auth("update", "{domainName}:{entityName}", false, "") + rPutEntity.Expected("NO_CONTENT") + rPutEntity.Exception("BAD_REQUEST", "ResourceError", "") + rPutEntity.Exception("CONFLICT", "ResourceError", "") + rPutEntity.Exception("FORBIDDEN", "ResourceError", "") + rPutEntity.Exception("NOT_FOUND", "ResourceError", "") + rPutEntity.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutEntity.Build()) + + rGetEntity := rdl.NewResourceBuilder("Entity", "GET", "/domain/{domainName}/entity/{entityName}") + rGetEntity.Comment("Get a entity from a domain. open for all authenticated users to read") + rGetEntity.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetEntity.Input("entityName", "EntityName", true, "", "", false, nil, "name of entity") + rGetEntity.Auth("", "", true, "") + rGetEntity.Exception("BAD_REQUEST", "ResourceError", "") + rGetEntity.Exception("FORBIDDEN", "ResourceError", "") + rGetEntity.Exception("NOT_FOUND", "ResourceError", "") + rGetEntity.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetEntity.Build()) + + rDeleteEntity := rdl.NewResourceBuilder("Entity", "DELETE", "/domain/{domainName}/entity/{entityName}") + rDeleteEntity.Comment("Delete the entity from the domain. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeleteEntity.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rDeleteEntity.Input("entityName", "EntityName", true, "", "", false, nil, "name of entity") + rDeleteEntity.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteEntity.Auth("delete", "{domainName}:{entityName}", false, "") + rDeleteEntity.Expected("NO_CONTENT") + rDeleteEntity.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteEntity.Exception("CONFLICT", "ResourceError", "") + rDeleteEntity.Exception("FORBIDDEN", "ResourceError", "") + rDeleteEntity.Exception("NOT_FOUND", "ResourceError", "") + rDeleteEntity.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteEntity.Build()) + + rGetEntityList := rdl.NewResourceBuilder("EntityList", "GET", "/domain/{domainName}/entity") + rGetEntityList.Comment("Enumerate entities provisioned in this domain.") + rGetEntityList.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetEntityList.Auth("", "", true, "") + rGetEntityList.Exception("BAD_REQUEST", "ResourceError", "") + rGetEntityList.Exception("NOT_FOUND", "ResourceError", "") + rGetEntityList.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetEntityList.Build()) + + rGetRoleList := rdl.NewResourceBuilder("RoleList", "GET", "/domain/{domainName}/role") + rGetRoleList.Comment("Enumerate roles provisioned in this domain.") + rGetRoleList.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetRoleList.Input("limit", "Int32", false, "limit", "", true, nil, "restrict the number of results in this call") + rGetRoleList.Input("skip", "String", false, "skip", "", true, nil, "restrict the set to those after the specified \"next\" token returned from a previous call") + rGetRoleList.Auth("", "", true, "") + rGetRoleList.Exception("BAD_REQUEST", "ResourceError", "") + rGetRoleList.Exception("FORBIDDEN", "ResourceError", "") + rGetRoleList.Exception("NOT_FOUND", "ResourceError", "") + rGetRoleList.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetRoleList.Build()) + + rGetRoles := rdl.NewResourceBuilder("Roles", "GET", "/domain/{domainName}/roles") + rGetRoles.Comment("Get the list of all roles in a domain with optional flag whether or not include members") + rGetRoles.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetRoles.Input("members", "Bool", false, "members", "", true, false, "return list of members in the role") + rGetRoles.Auth("", "", true, "") + rGetRoles.Exception("BAD_REQUEST", "ResourceError", "") + rGetRoles.Exception("NOT_FOUND", "ResourceError", "") + rGetRoles.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetRoles.Build()) + + rGetRole := rdl.NewResourceBuilder("Role", "GET", "/domain/{domainName}/role/{roleName}") + rGetRole.Comment("Get the specified role in the domain.") + rGetRole.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetRole.Input("roleName", "EntityName", true, "", "", false, nil, "name of the role to be retrieved") + rGetRole.Input("auditLog", "Bool", false, "auditLog", "", true, false, "flag to indicate whether or not to return role audit log") + rGetRole.Input("expand", "Bool", false, "expand", "", true, false, "expand delegated trust roles and return trusted members") + rGetRole.Auth("", "", true, "") + rGetRole.Exception("BAD_REQUEST", "ResourceError", "") + rGetRole.Exception("FORBIDDEN", "ResourceError", "") + rGetRole.Exception("NOT_FOUND", "ResourceError", "") + rGetRole.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetRole.Build()) + + rPutRole := rdl.NewResourceBuilder("Role", "PUT", "/domain/{domainName}/role/{roleName}") + rPutRole.Comment("Create/update the specified role.") + rPutRole.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rPutRole.Input("roleName", "EntityName", true, "", "", false, nil, "name of the role to be added/updated") + rPutRole.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutRole.Input("role", "Role", false, "", "", false, nil, "Role object to be added/updated in the domain") + rPutRole.Auth("update", "{domainName}:role.{roleName}", false, "") + rPutRole.Expected("NO_CONTENT") + rPutRole.Exception("BAD_REQUEST", "ResourceError", "") + rPutRole.Exception("CONFLICT", "ResourceError", "") + rPutRole.Exception("FORBIDDEN", "ResourceError", "") + rPutRole.Exception("NOT_FOUND", "ResourceError", "") + rPutRole.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutRole.Build()) + + rDeleteRole := rdl.NewResourceBuilder("Role", "DELETE", "/domain/{domainName}/role/{roleName}") + rDeleteRole.Comment("Delete the specified role. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeleteRole.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rDeleteRole.Input("roleName", "EntityName", true, "", "", false, nil, "name of the role to be deleted") + rDeleteRole.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteRole.Auth("delete", "{domainName}:role.{roleName}", false, "") + rDeleteRole.Expected("NO_CONTENT") + rDeleteRole.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteRole.Exception("CONFLICT", "ResourceError", "") + rDeleteRole.Exception("FORBIDDEN", "ResourceError", "") + rDeleteRole.Exception("NOT_FOUND", "ResourceError", "") + rDeleteRole.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteRole.Build()) + + rGetMembership := rdl.NewResourceBuilder("Membership", "GET", "/domain/{domainName}/role/{roleName}/member/{memberName}") + rGetMembership.Comment("Get the membership status for a specified user in a role.") + rGetMembership.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetMembership.Input("roleName", "EntityName", true, "", "", false, nil, "name of the role") + rGetMembership.Input("memberName", "ResourceName", true, "", "", false, nil, "user name to be checked for membership") + rGetMembership.Auth("", "", true, "") + rGetMembership.Exception("BAD_REQUEST", "ResourceError", "") + rGetMembership.Exception("FORBIDDEN", "ResourceError", "") + rGetMembership.Exception("NOT_FOUND", "ResourceError", "") + rGetMembership.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetMembership.Build()) + + rPutMembership := rdl.NewResourceBuilder("Membership", "PUT", "/domain/{domainName}/role/{roleName}/member/{memberName}") + rPutMembership.Comment("Add the specified user to the role's member list.") + rPutMembership.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rPutMembership.Input("roleName", "EntityName", true, "", "", false, nil, "name of the role") + rPutMembership.Input("memberName", "ResourceName", true, "", "", false, nil, "name of the user to be added as a member") + rPutMembership.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutMembership.Input("membership", "Membership", false, "", "", false, nil, "Membership object (must contain role/member names as specified in the URI)") + rPutMembership.Auth("update", "{domainName}:role.{roleName}", false, "") + rPutMembership.Expected("NO_CONTENT") + rPutMembership.Exception("BAD_REQUEST", "ResourceError", "") + rPutMembership.Exception("CONFLICT", "ResourceError", "") + rPutMembership.Exception("FORBIDDEN", "ResourceError", "") + rPutMembership.Exception("NOT_FOUND", "ResourceError", "") + rPutMembership.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutMembership.Build()) + + rDeleteMembership := rdl.NewResourceBuilder("Membership", "DELETE", "/domain/{domainName}/role/{roleName}/member/{memberName}") + rDeleteMembership.Comment("Delete the specified role membership. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeleteMembership.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rDeleteMembership.Input("roleName", "EntityName", true, "", "", false, nil, "name of the role") + rDeleteMembership.Input("memberName", "ResourceName", true, "", "", false, nil, "name of the user to be removed as a member") + rDeleteMembership.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteMembership.Auth("update", "{domainName}:role.{roleName}", false, "") + rDeleteMembership.Expected("NO_CONTENT") + rDeleteMembership.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteMembership.Exception("CONFLICT", "ResourceError", "") + rDeleteMembership.Exception("FORBIDDEN", "ResourceError", "") + rDeleteMembership.Exception("NOT_FOUND", "ResourceError", "") + rDeleteMembership.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteMembership.Build()) + + rPutDefaultAdmins := rdl.NewResourceBuilder("DefaultAdmins", "PUT", "/domain/{domainName}/admins") + rPutDefaultAdmins.Comment("Verify and, if necessary, fix domain roles and policies to make sure the given set of users have administrative access to the domain. This request is only restricted to \"sys.auth\" domain administrators and can be used when the domain administrators incorrectly have blocked their own access to their domains.") + rPutDefaultAdmins.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rPutDefaultAdmins.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutDefaultAdmins.Input("defaultAdmins", "DefaultAdmins", false, "", "", false, nil, "list of domain administrators") + rPutDefaultAdmins.Auth("update", "sys.auth:domain", false, "") + rPutDefaultAdmins.Expected("NO_CONTENT") + rPutDefaultAdmins.Exception("BAD_REQUEST", "ResourceError", "") + rPutDefaultAdmins.Exception("FORBIDDEN", "ResourceError", "") + rPutDefaultAdmins.Exception("NOT_FOUND", "ResourceError", "") + rPutDefaultAdmins.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutDefaultAdmins.Build()) + + rGetPolicyList := rdl.NewResourceBuilder("PolicyList", "GET", "/domain/{domainName}/policy") + rGetPolicyList.Comment("List policies provisioned in this namespace.") + rGetPolicyList.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetPolicyList.Input("limit", "Int32", false, "limit", "", true, nil, "restrict the number of results in this call") + rGetPolicyList.Input("skip", "String", false, "skip", "", true, nil, "restrict the set to those after the specified \"next\" token returned from a previous call") + rGetPolicyList.Auth("", "", true, "") + rGetPolicyList.Exception("BAD_REQUEST", "ResourceError", "") + rGetPolicyList.Exception("FORBIDDEN", "ResourceError", "") + rGetPolicyList.Exception("NOT_FOUND", "ResourceError", "") + rGetPolicyList.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetPolicyList.Build()) + + rGetPolicies := rdl.NewResourceBuilder("Policies", "GET", "/domain/{domainName}/policies") + rGetPolicies.Comment("List policies provisioned in this namespace.") + rGetPolicies.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetPolicies.Input("assertions", "Bool", false, "assertions", "", true, false, "return list of assertions in the policy") + rGetPolicies.Auth("", "", true, "") + rGetPolicies.Exception("BAD_REQUEST", "ResourceError", "") + rGetPolicies.Exception("NOT_FOUND", "ResourceError", "") + rGetPolicies.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetPolicies.Build()) + + rGetPolicy := rdl.NewResourceBuilder("Policy", "GET", "/domain/{domainName}/policy/{policyName}") + rGetPolicy.Comment("Read the specified policy.") + rGetPolicy.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetPolicy.Input("policyName", "EntityName", true, "", "", false, nil, "name of the policy to be retrieved") + rGetPolicy.Auth("", "", true, "") + rGetPolicy.Exception("BAD_REQUEST", "ResourceError", "") + rGetPolicy.Exception("FORBIDDEN", "ResourceError", "") + rGetPolicy.Exception("NOT_FOUND", "ResourceError", "") + rGetPolicy.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetPolicy.Build()) + + rPutPolicy := rdl.NewResourceBuilder("Policy", "PUT", "/domain/{domainName}/policy/{policyName}") + rPutPolicy.Comment("Create or update the specified policy.") + rPutPolicy.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rPutPolicy.Input("policyName", "EntityName", true, "", "", false, nil, "name of the policy to be added/updated") + rPutPolicy.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutPolicy.Input("policy", "Policy", false, "", "", false, nil, "Policy object to be added or updated in the domain") + rPutPolicy.Auth("update", "{domainName}:policy.{policyName}", false, "") + rPutPolicy.Expected("NO_CONTENT") + rPutPolicy.Exception("BAD_REQUEST", "ResourceError", "") + rPutPolicy.Exception("CONFLICT", "ResourceError", "") + rPutPolicy.Exception("FORBIDDEN", "ResourceError", "") + rPutPolicy.Exception("NOT_FOUND", "ResourceError", "") + rPutPolicy.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutPolicy.Build()) + + rDeletePolicy := rdl.NewResourceBuilder("Policy", "DELETE", "/domain/{domainName}/policy/{policyName}") + rDeletePolicy.Comment("Delete the specified policy. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeletePolicy.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rDeletePolicy.Input("policyName", "EntityName", true, "", "", false, nil, "name of the policy to be deleted") + rDeletePolicy.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeletePolicy.Auth("delete", "{domainName}:policy.{policyName}", false, "") + rDeletePolicy.Expected("NO_CONTENT") + rDeletePolicy.Exception("BAD_REQUEST", "ResourceError", "") + rDeletePolicy.Exception("CONFLICT", "ResourceError", "") + rDeletePolicy.Exception("FORBIDDEN", "ResourceError", "") + rDeletePolicy.Exception("NOT_FOUND", "ResourceError", "") + rDeletePolicy.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeletePolicy.Build()) + + rGetAssertion := rdl.NewResourceBuilder("Assertion", "GET", "/domain/{domainName}/policy/{policyName}/assertion/{assertionId}") + rGetAssertion.Comment("Get the assertion details with specified id in the given policy") + rGetAssertion.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetAssertion.Input("policyName", "EntityName", true, "", "", false, nil, "name of the policy") + rGetAssertion.Input("assertionId", "Int64", true, "", "", false, nil, "assertion id") + rGetAssertion.Auth("", "", true, "") + rGetAssertion.Exception("BAD_REQUEST", "ResourceError", "") + rGetAssertion.Exception("FORBIDDEN", "ResourceError", "") + rGetAssertion.Exception("NOT_FOUND", "ResourceError", "") + rGetAssertion.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetAssertion.Build()) + + rPutAssertion := rdl.NewResourceBuilder("Assertion", "PUT", "/domain/{domainName}/policy/{policyName}/assertion") + rPutAssertion.Comment("Add the specified assertion to the given policy") + rPutAssertion.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rPutAssertion.Input("policyName", "EntityName", true, "", "", false, nil, "name of the policy") + rPutAssertion.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutAssertion.Input("assertion", "Assertion", false, "", "", false, nil, "Assertion object to be added to the given policy") + rPutAssertion.Auth("update", "{domainName}:policy.{policyName}", false, "") + rPutAssertion.Exception("BAD_REQUEST", "ResourceError", "") + rPutAssertion.Exception("CONFLICT", "ResourceError", "") + rPutAssertion.Exception("FORBIDDEN", "ResourceError", "") + rPutAssertion.Exception("NOT_FOUND", "ResourceError", "") + rPutAssertion.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutAssertion.Build()) + + rDeleteAssertion := rdl.NewResourceBuilder("Assertion", "DELETE", "/domain/{domainName}/policy/{policyName}/assertion/{assertionId}") + rDeleteAssertion.Comment("Delete the specified policy assertion. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeleteAssertion.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rDeleteAssertion.Input("policyName", "EntityName", true, "", "", false, nil, "name of the policy") + rDeleteAssertion.Input("assertionId", "Int64", true, "", "", false, nil, "assertion id") + rDeleteAssertion.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteAssertion.Auth("update", "{domainName}:policy.{policyName}", false, "") + rDeleteAssertion.Expected("NO_CONTENT") + rDeleteAssertion.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteAssertion.Exception("CONFLICT", "ResourceError", "") + rDeleteAssertion.Exception("FORBIDDEN", "ResourceError", "") + rDeleteAssertion.Exception("NOT_FOUND", "ResourceError", "") + rDeleteAssertion.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteAssertion.Build()) + + rPutServiceIdentity := rdl.NewResourceBuilder("ServiceIdentity", "PUT", "/domain/{domain}/service/{service}") + rPutServiceIdentity.Comment("Register the specified ServiceIdentity in the specified domain") + rPutServiceIdentity.Input("domain", "DomainName", true, "", "", false, nil, "name of the domain") + rPutServiceIdentity.Input("service", "SimpleName", true, "", "", false, nil, "name of the service") + rPutServiceIdentity.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutServiceIdentity.Input("detail", "ServiceIdentity", false, "", "", false, nil, "ServiceIdentity object to be added/updated in the domain") + rPutServiceIdentity.Auth("update", "{domain}:service", false, "") + rPutServiceIdentity.Expected("NO_CONTENT") + rPutServiceIdentity.Exception("BAD_REQUEST", "ResourceError", "") + rPutServiceIdentity.Exception("CONFLICT", "ResourceError", "") + rPutServiceIdentity.Exception("FORBIDDEN", "ResourceError", "") + rPutServiceIdentity.Exception("NOT_FOUND", "ResourceError", "") + rPutServiceIdentity.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutServiceIdentity.Build()) + + rGetServiceIdentity := rdl.NewResourceBuilder("ServiceIdentity", "GET", "/domain/{domain}/service/{service}") + rGetServiceIdentity.Comment("Get info for the specified ServiceIdentity.") + rGetServiceIdentity.Input("domain", "DomainName", true, "", "", false, nil, "name of the domain") + rGetServiceIdentity.Input("service", "SimpleName", true, "", "", false, nil, "name of the service to be retrieved") + rGetServiceIdentity.Auth("", "", true, "") + rGetServiceIdentity.Exception("BAD_REQUEST", "ResourceError", "") + rGetServiceIdentity.Exception("FORBIDDEN", "ResourceError", "") + rGetServiceIdentity.Exception("NOT_FOUND", "ResourceError", "") + rGetServiceIdentity.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetServiceIdentity.Build()) + + rDeleteServiceIdentity := rdl.NewResourceBuilder("ServiceIdentity", "DELETE", "/domain/{domain}/service/{service}") + rDeleteServiceIdentity.Comment("Delete the specified ServiceIdentity. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeleteServiceIdentity.Input("domain", "DomainName", true, "", "", false, nil, "name of the domain") + rDeleteServiceIdentity.Input("service", "SimpleName", true, "", "", false, nil, "name of the service to be deleted") + rDeleteServiceIdentity.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteServiceIdentity.Auth("delete", "{domain}:service", false, "") + rDeleteServiceIdentity.Expected("NO_CONTENT") + rDeleteServiceIdentity.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteServiceIdentity.Exception("CONFLICT", "ResourceError", "") + rDeleteServiceIdentity.Exception("FORBIDDEN", "ResourceError", "") + rDeleteServiceIdentity.Exception("NOT_FOUND", "ResourceError", "") + rDeleteServiceIdentity.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteServiceIdentity.Build()) + + rGetServiceIdentities := rdl.NewResourceBuilder("ServiceIdentities", "GET", "/domain/{domainName}/services") + rGetServiceIdentities.Comment("Retrieve list of service identities") + rGetServiceIdentities.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetServiceIdentities.Input("publickeys", "Bool", false, "publickeys", "", true, false, "return list of public keys in the service") + rGetServiceIdentities.Input("hosts", "Bool", false, "hosts", "", true, false, "return list of hosts in the service") + rGetServiceIdentities.Auth("", "", true, "") + rGetServiceIdentities.Exception("BAD_REQUEST", "ResourceError", "") + rGetServiceIdentities.Exception("NOT_FOUND", "ResourceError", "") + rGetServiceIdentities.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetServiceIdentities.Build()) + + rGetServiceIdentityList := rdl.NewResourceBuilder("ServiceIdentityList", "GET", "/domain/{domainName}/service") + rGetServiceIdentityList.Comment("Enumerate services provisioned in this domain.") + rGetServiceIdentityList.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetServiceIdentityList.Input("limit", "Int32", false, "limit", "", true, nil, "restrict the number of results in this call") + rGetServiceIdentityList.Input("skip", "String", false, "skip", "", true, nil, "restrict the set to those after the specified \"next\" token returned from a previous call") + rGetServiceIdentityList.Auth("", "", true, "") + rGetServiceIdentityList.Exception("BAD_REQUEST", "ResourceError", "") + rGetServiceIdentityList.Exception("FORBIDDEN", "ResourceError", "") + rGetServiceIdentityList.Exception("NOT_FOUND", "ResourceError", "") + rGetServiceIdentityList.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetServiceIdentityList.Build()) + + rGetPublicKeyEntry := rdl.NewResourceBuilder("PublicKeyEntry", "GET", "/domain/{domain}/service/{service}/publickey/{id}") + rGetPublicKeyEntry.Comment("Retrieve the specified public key from the service.") + rGetPublicKeyEntry.Input("domain", "DomainName", true, "", "", false, nil, "name of the domain") + rGetPublicKeyEntry.Input("service", "SimpleName", true, "", "", false, nil, "name of the service") + rGetPublicKeyEntry.Input("id", "String", true, "", "", false, nil, "the identifier of the public key to be retrieved") + rGetPublicKeyEntry.Auth("", "", true, "") + rGetPublicKeyEntry.Exception("BAD_REQUEST", "ResourceError", "") + rGetPublicKeyEntry.Exception("FORBIDDEN", "ResourceError", "") + rGetPublicKeyEntry.Exception("NOT_FOUND", "ResourceError", "") + rGetPublicKeyEntry.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetPublicKeyEntry.Build()) + + rPutPublicKeyEntry := rdl.NewResourceBuilder("PublicKeyEntry", "PUT", "/domain/{domain}/service/{service}/publickey/{id}") + rPutPublicKeyEntry.Comment("Add the specified public key to the service.") + rPutPublicKeyEntry.Input("domain", "DomainName", true, "", "", false, nil, "name of the domain") + rPutPublicKeyEntry.Input("service", "SimpleName", true, "", "", false, nil, "name of the service") + rPutPublicKeyEntry.Input("id", "String", true, "", "", false, nil, "the identifier of the public key to be added") + rPutPublicKeyEntry.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutPublicKeyEntry.Input("publicKeyEntry", "PublicKeyEntry", false, "", "", false, nil, "PublicKeyEntry object to be added/updated in the service") + rPutPublicKeyEntry.Auth("update", "{domain}:service.{service}", false, "") + rPutPublicKeyEntry.Expected("NO_CONTENT") + rPutPublicKeyEntry.Exception("BAD_REQUEST", "ResourceError", "") + rPutPublicKeyEntry.Exception("CONFLICT", "ResourceError", "") + rPutPublicKeyEntry.Exception("FORBIDDEN", "ResourceError", "") + rPutPublicKeyEntry.Exception("NOT_FOUND", "ResourceError", "") + rPutPublicKeyEntry.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutPublicKeyEntry.Build()) + + rDeletePublicKeyEntry := rdl.NewResourceBuilder("PublicKeyEntry", "DELETE", "/domain/{domain}/service/{service}/publickey/{id}") + rDeletePublicKeyEntry.Comment("Remove the specified public key from the service. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeletePublicKeyEntry.Input("domain", "DomainName", true, "", "", false, nil, "name of the domain") + rDeletePublicKeyEntry.Input("service", "SimpleName", true, "", "", false, nil, "name of the service") + rDeletePublicKeyEntry.Input("id", "String", true, "", "", false, nil, "the identifier of the public key to be deleted") + rDeletePublicKeyEntry.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeletePublicKeyEntry.Auth("update", "{domain}:service.{service}", false, "") + rDeletePublicKeyEntry.Expected("NO_CONTENT") + rDeletePublicKeyEntry.Exception("BAD_REQUEST", "ResourceError", "") + rDeletePublicKeyEntry.Exception("CONFLICT", "ResourceError", "") + rDeletePublicKeyEntry.Exception("FORBIDDEN", "ResourceError", "") + rDeletePublicKeyEntry.Exception("NOT_FOUND", "ResourceError", "") + rDeletePublicKeyEntry.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeletePublicKeyEntry.Build()) + + rPutTenancy := rdl.NewResourceBuilder("Tenancy", "PUT", "/domain/{domain}/tenancy/{service}") + rPutTenancy.Comment("Add a tenant for the specified service.") + rPutTenancy.Input("domain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rPutTenancy.Input("service", "ServiceName", true, "", "", false, nil, "name of the provider service") + rPutTenancy.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutTenancy.Input("detail", "Tenancy", false, "", "", false, nil, "tenancy object") + rPutTenancy.Auth("update", "{domain}:tenancy", false, "") + rPutTenancy.Expected("NO_CONTENT") + rPutTenancy.Exception("BAD_REQUEST", "ResourceError", "") + rPutTenancy.Exception("CONFLICT", "ResourceError", "") + rPutTenancy.Exception("FORBIDDEN", "ResourceError", "") + rPutTenancy.Exception("NOT_FOUND", "ResourceError", "") + rPutTenancy.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutTenancy.Build()) + + rGetTenancy := rdl.NewResourceBuilder("Tenancy", "GET", "/domain/{domain}/tenancy/{service}") + rGetTenancy.Comment("Retrieve the specified tenant.") + rGetTenancy.Input("domain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rGetTenancy.Input("service", "ServiceName", true, "", "", false, nil, "name of the provider service") + rGetTenancy.Auth("", "", true, "") + rGetTenancy.Exception("BAD_REQUEST", "ResourceError", "") + rGetTenancy.Exception("FORBIDDEN", "ResourceError", "") + rGetTenancy.Exception("NOT_FOUND", "ResourceError", "") + rGetTenancy.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetTenancy.Build()) + + rDeleteTenancy := rdl.NewResourceBuilder("Tenancy", "DELETE", "/domain/{domain}/tenancy/{service}") + rDeleteTenancy.Comment("Delete the tenant from the specified service. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeleteTenancy.Input("domain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rDeleteTenancy.Input("service", "ServiceName", true, "", "", false, nil, "name of the provider service") + rDeleteTenancy.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteTenancy.Auth("delete", "{domain}:tenancy", false, "") + rDeleteTenancy.Expected("NO_CONTENT") + rDeleteTenancy.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteTenancy.Exception("CONFLICT", "ResourceError", "") + rDeleteTenancy.Exception("FORBIDDEN", "ResourceError", "") + rDeleteTenancy.Exception("NOT_FOUND", "ResourceError", "") + rDeleteTenancy.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteTenancy.Build()) + + rPutTenancyResourceGroup := rdl.NewResourceBuilder("TenancyResourceGroup", "PUT", "/domain/{domain}/tenancy/{service}/resourceGroup/{resourceGroup}") + rPutTenancyResourceGroup.Comment("Add a new resource group for the tenant for the specified service.") + rPutTenancyResourceGroup.Input("domain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rPutTenancyResourceGroup.Input("service", "ServiceName", true, "", "", false, nil, "name of the provider service") + rPutTenancyResourceGroup.Input("resourceGroup", "EntityName", true, "", "", false, nil, "tenant resource group") + rPutTenancyResourceGroup.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutTenancyResourceGroup.Input("detail", "TenancyResourceGroup", false, "", "", false, nil, "tenancy resource group object") + rPutTenancyResourceGroup.Auth("update", "{domain}:tenancy.{service}", false, "") + rPutTenancyResourceGroup.Expected("NO_CONTENT") + rPutTenancyResourceGroup.Exception("BAD_REQUEST", "ResourceError", "") + rPutTenancyResourceGroup.Exception("CONFLICT", "ResourceError", "") + rPutTenancyResourceGroup.Exception("FORBIDDEN", "ResourceError", "") + rPutTenancyResourceGroup.Exception("NOT_FOUND", "ResourceError", "") + rPutTenancyResourceGroup.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutTenancyResourceGroup.Build()) + + rDeleteTenancyResourceGroup := rdl.NewResourceBuilder("TenancyResourceGroup", "DELETE", "/domain/{domain}/tenancy/{service}/resourceGroup/{resourceGroup}") + rDeleteTenancyResourceGroup.Comment("Delete the specified resource group for tenant from the specified service.") + rDeleteTenancyResourceGroup.Input("domain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rDeleteTenancyResourceGroup.Input("service", "ServiceName", true, "", "", false, nil, "name of the provider service") + rDeleteTenancyResourceGroup.Input("resourceGroup", "EntityName", true, "", "", false, nil, "tenant resource group") + rDeleteTenancyResourceGroup.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteTenancyResourceGroup.Auth("update", "{domain}:tenancy.{service}", false, "") + rDeleteTenancyResourceGroup.Expected("NO_CONTENT") + rDeleteTenancyResourceGroup.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteTenancyResourceGroup.Exception("CONFLICT", "ResourceError", "") + rDeleteTenancyResourceGroup.Exception("FORBIDDEN", "ResourceError", "") + rDeleteTenancyResourceGroup.Exception("NOT_FOUND", "ResourceError", "") + rDeleteTenancyResourceGroup.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteTenancyResourceGroup.Build()) + + rPutTenantRoles := rdl.NewResourceBuilder("TenantRoles", "PUT", "/domain/{domain}/service/{service}/tenant/{tenantDomain}") + rPutTenantRoles.Comment("Create/update set of roles for a given tenant.") + rPutTenantRoles.Input("domain", "DomainName", true, "", "", false, nil, "name of the provider domain") + rPutTenantRoles.Input("service", "SimpleName", true, "", "", false, nil, "name of the provider service") + rPutTenantRoles.Input("tenantDomain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rPutTenantRoles.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutTenantRoles.Input("detail", "TenantRoles", false, "", "", false, nil, "list of roles to be added/updated for the tenant") + rPutTenantRoles.Auth("update", "{domain}:tenant.{tenantDomain}", false, "") + rPutTenantRoles.Exception("BAD_REQUEST", "ResourceError", "") + rPutTenantRoles.Exception("CONFLICT", "ResourceError", "") + rPutTenantRoles.Exception("FORBIDDEN", "ResourceError", "") + rPutTenantRoles.Exception("NOT_FOUND", "ResourceError", "") + rPutTenantRoles.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutTenantRoles.Build()) + + rGetTenantRoles := rdl.NewResourceBuilder("TenantRoles", "GET", "/domain/{domain}/service/{service}/tenant/{tenantDomain}") + rGetTenantRoles.Comment("Retrieve the configured set of roles for the tenant.") + rGetTenantRoles.Input("domain", "DomainName", true, "", "", false, nil, "name of the provider domain") + rGetTenantRoles.Input("service", "SimpleName", true, "", "", false, nil, "name of the provider service") + rGetTenantRoles.Input("tenantDomain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rGetTenantRoles.Auth("", "", true, "") + rGetTenantRoles.Exception("BAD_REQUEST", "ResourceError", "") + rGetTenantRoles.Exception("FORBIDDEN", "ResourceError", "") + rGetTenantRoles.Exception("NOT_FOUND", "ResourceError", "") + rGetTenantRoles.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetTenantRoles.Build()) + + rDeleteTenantRoles := rdl.NewResourceBuilder("TenantRoles", "DELETE", "/domain/{domain}/service/{service}/tenant/{tenantDomain}") + rDeleteTenantRoles.Comment("Delete the configured set of roles for the tenant. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + rDeleteTenantRoles.Input("domain", "DomainName", true, "", "", false, nil, "name of the provider domain") + rDeleteTenantRoles.Input("service", "SimpleName", true, "", "", false, nil, "name of the provider service") + rDeleteTenantRoles.Input("tenantDomain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rDeleteTenantRoles.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteTenantRoles.Auth("delete", "{domain}:tenant.{tenantDomain}", false, "") + rDeleteTenantRoles.Expected("NO_CONTENT") + rDeleteTenantRoles.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteTenantRoles.Exception("CONFLICT", "ResourceError", "") + rDeleteTenantRoles.Exception("FORBIDDEN", "ResourceError", "") + rDeleteTenantRoles.Exception("NOT_FOUND", "ResourceError", "") + rDeleteTenantRoles.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteTenantRoles.Build()) + + rPutTenantResourceGroupRoles := rdl.NewResourceBuilder("TenantResourceGroupRoles", "PUT", "/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + rPutTenantResourceGroupRoles.Comment("Create/update set of roles for a given tenant and resource group") + rPutTenantResourceGroupRoles.Input("domain", "DomainName", true, "", "", false, nil, "name of the provider domain") + rPutTenantResourceGroupRoles.Input("service", "SimpleName", true, "", "", false, nil, "name of the provider service") + rPutTenantResourceGroupRoles.Input("tenantDomain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rPutTenantResourceGroupRoles.Input("resourceGroup", "EntityName", true, "", "", false, nil, "tenant resource group") + rPutTenantResourceGroupRoles.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutTenantResourceGroupRoles.Input("detail", "TenantResourceGroupRoles", false, "", "", false, nil, "list of roles to be added/updated for the tenant") + rPutTenantResourceGroupRoles.Auth("update", "{domain}:tenant.{tenantDomain}", false, "") + rPutTenantResourceGroupRoles.Exception("BAD_REQUEST", "ResourceError", "") + rPutTenantResourceGroupRoles.Exception("CONFLICT", "ResourceError", "") + rPutTenantResourceGroupRoles.Exception("FORBIDDEN", "ResourceError", "") + rPutTenantResourceGroupRoles.Exception("NOT_FOUND", "ResourceError", "") + rPutTenantResourceGroupRoles.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutTenantResourceGroupRoles.Build()) + + rGetTenantResourceGroupRoles := rdl.NewResourceBuilder("TenantResourceGroupRoles", "GET", "/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + rGetTenantResourceGroupRoles.Comment("Retrieve the configured set of roles for the tenant and resource group") + rGetTenantResourceGroupRoles.Input("domain", "DomainName", true, "", "", false, nil, "name of the provider domain") + rGetTenantResourceGroupRoles.Input("service", "SimpleName", true, "", "", false, nil, "name of the provider service") + rGetTenantResourceGroupRoles.Input("tenantDomain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rGetTenantResourceGroupRoles.Input("resourceGroup", "EntityName", true, "", "", false, nil, "tenant resource group") + rGetTenantResourceGroupRoles.Auth("", "", true, "") + rGetTenantResourceGroupRoles.Exception("BAD_REQUEST", "ResourceError", "") + rGetTenantResourceGroupRoles.Exception("FORBIDDEN", "ResourceError", "") + rGetTenantResourceGroupRoles.Exception("NOT_FOUND", "ResourceError", "") + rGetTenantResourceGroupRoles.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetTenantResourceGroupRoles.Build()) + + rDeleteTenantResourceGroupRoles := rdl.NewResourceBuilder("TenantResourceGroupRoles", "DELETE", "/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + rDeleteTenantResourceGroupRoles.Comment("Delete the configured set of roles for the tenant and resource group") + rDeleteTenantResourceGroupRoles.Input("domain", "DomainName", true, "", "", false, nil, "name of the provider domain") + rDeleteTenantResourceGroupRoles.Input("service", "SimpleName", true, "", "", false, nil, "name of the provider service") + rDeleteTenantResourceGroupRoles.Input("tenantDomain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rDeleteTenantResourceGroupRoles.Input("resourceGroup", "EntityName", true, "", "", false, nil, "tenant resource group") + rDeleteTenantResourceGroupRoles.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteTenantResourceGroupRoles.Auth("update", "{domain}:tenant.{tenantDomain}", false, "") + rDeleteTenantResourceGroupRoles.Expected("NO_CONTENT") + rDeleteTenantResourceGroupRoles.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteTenantResourceGroupRoles.Exception("CONFLICT", "ResourceError", "") + rDeleteTenantResourceGroupRoles.Exception("FORBIDDEN", "ResourceError", "") + rDeleteTenantResourceGroupRoles.Exception("NOT_FOUND", "ResourceError", "") + rDeleteTenantResourceGroupRoles.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteTenantResourceGroupRoles.Build()) + + rPutProviderResourceGroupRoles := rdl.NewResourceBuilder("ProviderResourceGroupRoles", "PUT", "/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + rPutProviderResourceGroupRoles.Comment("Create/update set of roles for a given provider and resource group") + rPutProviderResourceGroupRoles.Input("tenantDomain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rPutProviderResourceGroupRoles.Input("provDomain", "DomainName", true, "", "", false, nil, "name of the provider domain") + rPutProviderResourceGroupRoles.Input("provService", "SimpleName", true, "", "", false, nil, "name of the provider service") + rPutProviderResourceGroupRoles.Input("resourceGroup", "EntityName", true, "", "", false, nil, "tenant resource group") + rPutProviderResourceGroupRoles.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rPutProviderResourceGroupRoles.Input("detail", "ProviderResourceGroupRoles", false, "", "", false, nil, "list of roles to be added/updated for the provider") + rPutProviderResourceGroupRoles.Auth("update", "{tenantDomain}:tenancy.{provDomain}.{provService}", false, "") + rPutProviderResourceGroupRoles.Exception("BAD_REQUEST", "ResourceError", "") + rPutProviderResourceGroupRoles.Exception("CONFLICT", "ResourceError", "") + rPutProviderResourceGroupRoles.Exception("FORBIDDEN", "ResourceError", "") + rPutProviderResourceGroupRoles.Exception("NOT_FOUND", "ResourceError", "") + rPutProviderResourceGroupRoles.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPutProviderResourceGroupRoles.Build()) + + rGetProviderResourceGroupRoles := rdl.NewResourceBuilder("ProviderResourceGroupRoles", "GET", "/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + rGetProviderResourceGroupRoles.Comment("Retrieve the configured set of roles for the provider and resource group") + rGetProviderResourceGroupRoles.Input("tenantDomain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rGetProviderResourceGroupRoles.Input("provDomain", "DomainName", true, "", "", false, nil, "name of the provider domain") + rGetProviderResourceGroupRoles.Input("provService", "SimpleName", true, "", "", false, nil, "name of the provider service") + rGetProviderResourceGroupRoles.Input("resourceGroup", "EntityName", true, "", "", false, nil, "tenant resource group") + rGetProviderResourceGroupRoles.Auth("", "", true, "") + rGetProviderResourceGroupRoles.Exception("BAD_REQUEST", "ResourceError", "") + rGetProviderResourceGroupRoles.Exception("FORBIDDEN", "ResourceError", "") + rGetProviderResourceGroupRoles.Exception("NOT_FOUND", "ResourceError", "") + rGetProviderResourceGroupRoles.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetProviderResourceGroupRoles.Build()) + + rDeleteProviderResourceGroupRoles := rdl.NewResourceBuilder("ProviderResourceGroupRoles", "DELETE", "/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + rDeleteProviderResourceGroupRoles.Comment("Delete the configured set of roles for the provider and resource group") + rDeleteProviderResourceGroupRoles.Input("tenantDomain", "DomainName", true, "", "", false, nil, "name of the tenant domain") + rDeleteProviderResourceGroupRoles.Input("provDomain", "DomainName", true, "", "", false, nil, "name of the provider domain") + rDeleteProviderResourceGroupRoles.Input("provService", "SimpleName", true, "", "", false, nil, "name of the provider service") + rDeleteProviderResourceGroupRoles.Input("resourceGroup", "EntityName", true, "", "", false, nil, "tenant resource group") + rDeleteProviderResourceGroupRoles.Input("auditRef", "String", false, "", "Y-Audit-Ref", false, nil, "Audit param required(not empty) if domain auditEnabled is true.") + rDeleteProviderResourceGroupRoles.Auth("update", "{tenantDomain}:tenancy.{provDomain}.{provService}", false, "") + rDeleteProviderResourceGroupRoles.Expected("NO_CONTENT") + rDeleteProviderResourceGroupRoles.Exception("BAD_REQUEST", "ResourceError", "") + rDeleteProviderResourceGroupRoles.Exception("CONFLICT", "ResourceError", "") + rDeleteProviderResourceGroupRoles.Exception("FORBIDDEN", "ResourceError", "") + rDeleteProviderResourceGroupRoles.Exception("NOT_FOUND", "ResourceError", "") + rDeleteProviderResourceGroupRoles.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rDeleteProviderResourceGroupRoles.Build()) + + rGetAccess := rdl.NewResourceBuilder("Access", "GET", "/access/{action}/{resource}") + rGetAccess.Comment("Check access for the specified operation on the specified resource for the currently authenticated user. This is the slow centralized access for control-plane purposes. Use distributed mechanisms for decentralized (data-plane) access by fetching signed policies and role tokens for users.") + rGetAccess.Input("action", "ActionName", true, "", "", false, nil, "action as specified in the policy assertion, i.e. update or read") + rGetAccess.Input("resource", "YRN", true, "", "", false, nil, "the resource to check access against, i.e. \"media.news:articles\"") + rGetAccess.Input("domain", "DomainName", false, "domain", "", true, nil, "usually null. If present, it specifies an alternate domain for cross-domain trust relation") + rGetAccess.Input("checkPrincipal", "EntityName", false, "principal", "", true, nil, "usually null. If present, carry out the access check for this principal") + rGetAccess.Auth("", "", true, "") + rGetAccess.Exception("BAD_REQUEST", "ResourceError", "") + rGetAccess.Exception("FORBIDDEN", "ResourceError", "") + rGetAccess.Exception("NOT_FOUND", "ResourceError", "") + rGetAccess.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetAccess.Build()) + + rGetAccessExt := rdl.NewResourceBuilder("Access", "GET", "/access/{action}") + rGetAccessExt.Name("GetAccessExt") + rGetAccessExt.Input("action", "ActionName", true, "", "", false, nil, "action as specified in the policy assertion, i.e. update or read") + rGetAccessExt.Input("resource", "String", false, "resource", "", false, nil, "the resource to check access against, i.e. \"media.news:articles\"") + rGetAccessExt.Input("domain", "DomainName", false, "domain", "", true, nil, "usually null. If present, it specifies an alternate domain for cross-domain trust relation") + rGetAccessExt.Input("checkPrincipal", "EntityName", false, "principal", "", true, nil, "usually null. If present, carry out the access check for this principal") + rGetAccessExt.Auth("", "", true, "") + rGetAccessExt.Exception("BAD_REQUEST", "ResourceError", "") + rGetAccessExt.Exception("FORBIDDEN", "ResourceError", "") + rGetAccessExt.Exception("NOT_FOUND", "ResourceError", "") + rGetAccessExt.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetAccessExt.Build()) + + rGetResourceAccessList := rdl.NewResourceBuilder("ResourceAccessList", "GET", "/resource") + rGetResourceAccessList.Comment("Return list of resources that the given principal has access to. Even though the principal is marked as optional, it must be specified unless the caller has authorization from sys.auth domain to check access for all user principals. (action: access, resource: resource-lookup-all)") + rGetResourceAccessList.Input("principal", "EntityName", false, "principal", "", true, nil, "specifies principal to query the resource list for") + rGetResourceAccessList.Input("action", "ActionName", false, "action", "", true, nil, "action as specified in the policy assertion") + rGetResourceAccessList.Auth("", "", true, "") + rGetResourceAccessList.Exception("BAD_REQUEST", "ResourceError", "") + rGetResourceAccessList.Exception("FORBIDDEN", "ResourceError", "") + rGetResourceAccessList.Exception("NOT_FOUND", "ResourceError", "") + rGetResourceAccessList.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetResourceAccessList.Build()) + + rGetSignedDomains := rdl.NewResourceBuilder("SignedDomains", "GET", "/sys/modified_domains") + rGetSignedDomains.Comment("Retrieve the list of modified domains since the specified timestamp. The server will return the list of all modified domains and the latest modification timestamp as the value of the ETag header. The client will need to use this value during its next call to request the changes since the previous request. When metaonly set to true, dont add roles, policies or services, dont sign") + rGetSignedDomains.Input("domain", "DomainName", false, "domain", "", true, nil, "filter the domain list only to the specified name") + rGetSignedDomains.Input("metaOnly", "String", false, "metaonly", "", true, nil, "valid values are \"true\" or \"false\"") + rGetSignedDomains.Input("matchingTag", "String", false, "", "If-None-Match", false, nil, "Retrieved from the previous request, this timestamp specifies to the server to return any domains modified since this time") + rGetSignedDomains.Output("tag", "String", "ETag", false, "") + rGetSignedDomains.Auth("", "", true, "") + rGetSignedDomains.Exception("FORBIDDEN", "ResourceError", "") + rGetSignedDomains.Exception("NOT_FOUND", "ResourceError", "") + rGetSignedDomains.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetSignedDomains.Build()) + + rGetUserToken := rdl.NewResourceBuilder("UserToken", "GET", "/user/{userName}/token") + rGetUserToken.Comment("Return a user/principal token for the specified authenticated user. Typical authenticated users with their native credentials are not allowed to update their domain data. They must first obtain a UserToken and then use that token for authentication and authorization of their update requests.") + rGetUserToken.Input("userName", "SimpleName", true, "", "", false, nil, "name of the user") + rGetUserToken.Input("serviceNames", "String", false, "services", "", true, nil, "comma separated list of on-behalf-of service names") + rGetUserToken.Auth("", "", true, "") + rGetUserToken.Exception("FORBIDDEN", "ResourceError", "") + rGetUserToken.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetUserToken.Build()) + + rOptionsUserToken := rdl.NewResourceBuilder("UserToken", "OPTIONS", "/user/{userName}/token") + rOptionsUserToken.Comment("CORS (Cross-Origin Resource Sharing) support to allow Provider Services to obtain AuthorizedService Tokens on behalf of Tenant administrators") + rOptionsUserToken.Input("userName", "SimpleName", true, "", "", false, nil, "name of the user") + rOptionsUserToken.Input("serviceNames", "String", false, "services", "", true, nil, "comma separated list of on-behalf-of service names") + rOptionsUserToken.Exception("BAD_REQUEST", "ResourceError", "") + sb.AddResource(rOptionsUserToken.Build()) + + rGetServicePrincipal := rdl.NewResourceBuilder("ServicePrincipal", "GET", "/principal") + rGetServicePrincipal.Comment("Return a ServicePrincipal object if the serviceToken is valid. This request provides a simple operation that an external application can execute to validate a service token.") + rGetServicePrincipal.Auth("", "", true, "") + rGetServicePrincipal.Exception("BAD_REQUEST", "ResourceError", "") + rGetServicePrincipal.Exception("FORBIDDEN", "ResourceError", "") + rGetServicePrincipal.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetServicePrincipal.Build()) + + rGetServerTemplateList := rdl.NewResourceBuilder("ServerTemplateList", "GET", "/template") + rGetServerTemplateList.Comment("Get the list of solution templates defined in the server") + rGetServerTemplateList.Auth("", "", true, "") + rGetServerTemplateList.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetServerTemplateList.Build()) + + rGetTemplate := rdl.NewResourceBuilder("Template", "GET", "/template/{template}") + rGetTemplate.Comment("Get solution template details. Includes the roles and policies that will be automatically provisioned when the template is applied to a domain") + rGetTemplate.Input("template", "SimpleName", true, "", "", false, nil, "name of the solution template") + rGetTemplate.Auth("", "", true, "") + rGetTemplate.Exception("BAD_REQUEST", "ResourceError", "") + rGetTemplate.Exception("NOT_FOUND", "ResourceError", "") + rGetTemplate.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetTemplate.Build()) + + schema = sb.Build() +} + +func ZMSSchema() *rdl.Schema { + return schema +} diff --git a/clients/go/zts/Makefile b/clients/go/zts/Makefile new file mode 100644 index 00000000000..c3a2e703703 --- /dev/null +++ b/clients/go/zts/Makefile @@ -0,0 +1,20 @@ +RDL_FILE=../../../core/zts/src/main/rdl/ZTS.rdl +RDL_LIB=github.com/ardielle/ardielle-go/rdl + +export GOPATH=$(PWD) + +all: model.go client.go build + +build: src/$(RDL_LIB) + +src/$(RDL_LIB): + go get $(RDL_LIB) + +model.go: $(RDL_FILE) + rdl -ps generate -t -o $@ go-model $(RDL_FILE) + +client.go: $(RDL_FILE) + rdl -ps generate -t -o $@ go-client $(RDL_FILE) + +clean:: + rm -rf model.go client.go zts_schema.go *~ ./src diff --git a/clients/go/zts/README.md b/clients/go/zts/README.md new file mode 100644 index 00000000000..d55f6c12f2d --- /dev/null +++ b/clients/go/zts/README.md @@ -0,0 +1,36 @@ +# zts-go-client + +A Go client library to talk to Athenz ZTS. + +The model.go and client.go files are generated from zts_core, and checked in so users of this library need not know that. + +Release Notes: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Version 1.0 (2016-09-06) + - Initial opensource release + +## Usage + +To get it into your workspace: + + go get github.com/yahoo/athenz/clients/go/zts + +Then in your Go code: + + import ( + zts "github.com/yahoo/athenz/clients/go/zts" + ) + func main() { + var principal rdl.Principal /* NToken */ + ... + client := zts.NewClient() + client.AddCredentials(principal.GetHTTPHeaderName(), principal.GetCredentials()) + svc, err := client.GetServiceIdentity("athenz", "storage") + ... + } + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/clients/go/zts/client.go b/clients/go/zts/client.go new file mode 100644 index 00000000000..c00142a6a68 --- /dev/null +++ b/clients/go/zts/client.go @@ -0,0 +1,755 @@ +// +// This file generated by rdl 1.4.8 +// + +package zts + +import ( + "bytes" + "encoding/json" + "fmt" + rdl "github.com/ardielle/ardielle-go/rdl" + "io" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" + "time" +) + +var _ = json.Marshal +var _ = fmt.Printf +var _ = rdl.BaseTypeAny +var _ = ioutil.NopCloser + +type ZTSClient struct { + URL string + Transport http.RoundTripper + CredsHeader *string + CredsToken *string + Timeout time.Duration +} + +// NewClient creates and returns a new HTTP client object for the ZTS service +func NewClient(url string, transport http.RoundTripper) ZTSClient { + return ZTSClient{url, transport, nil, nil, 0} +} + +// AddCredentials adds the credentials to the client for subsequent requests. +func (client *ZTSClient) AddCredentials(header string, token string) { + client.CredsHeader = &header + client.CredsToken = &token +} + +func (client ZTSClient) getClient() *http.Client { + var c *http.Client + if client.Transport != nil { + c = &http.Client{Transport: client.Transport} + } else { + c = &http.Client{} + } + if client.Timeout > 0 { + c.Timeout = client.Timeout + } + return c +} + +func (client ZTSClient) addAuthHeader(req *http.Request) { + if client.CredsHeader != nil && client.CredsToken != nil { + if strings.HasPrefix(*client.CredsHeader, "Cookie.") { + req.Header.Add("Cookie", (*client.CredsHeader)[7:]+"="+*client.CredsToken) + } else { + req.Header.Add(*client.CredsHeader, *client.CredsToken) + } + } +} + +func (client ZTSClient) httpGet(url string, headers map[string]string) (*http.Response, error) { + hclient := client.getClient() + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func (client ZTSClient) httpDelete(url string, headers map[string]string) (*http.Response, error) { + hclient := client.getClient() + req, err := http.NewRequest("DELETE", url, nil) + if err != nil { + return nil, err + } + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func (client ZTSClient) httpPut(url string, headers map[string]string, body []byte) (*http.Response, error) { + contentReader := bytes.NewReader(body) + hclient := client.getClient() + req, err := http.NewRequest("PUT", url, contentReader) + if err != nil { + return nil, err + } + req.Header.Add("Content-type", "application/json") + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func (client ZTSClient) httpPost(url string, headers map[string]string, body []byte) (*http.Response, error) { + contentReader := bytes.NewReader(body) + hclient := client.getClient() + req, err := http.NewRequest("POST", url, contentReader) + if err != nil { + return nil, err + } + req.Header.Add("Content-type", "application/json") + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func (client ZTSClient) httpPatch(url string, headers map[string]string, body []byte) (*http.Response, error) { + contentReader := bytes.NewReader(body) + hclient := client.getClient() + req, err := http.NewRequest("PATCH", url, contentReader) + if err != nil { + return nil, err + } + req.Header.Add("Content-type", "application/json") + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func (client ZTSClient) httpOptions(url string, headers map[string]string, body []byte) (*http.Response, error) { + var contentReader io.Reader = nil + if body != nil { + contentReader = bytes.NewReader(body) + } + hclient := client.getClient() + req, err := http.NewRequest("OPTIONS", url, contentReader) + if err != nil { + return nil, err + } + if contentReader != nil { + req.Header.Add("Content-type", "application/json") + } + client.addAuthHeader(req) + if headers != nil { + for k, v := range headers { + req.Header.Add(k, v) + } + } + return hclient.Do(req) +} + +func encodeStringParam(name string, val string, def string) string { + if val == def { + return "" + } + return "&" + name + "=" + url.QueryEscape(val) +} +func encodeBoolParam(name string, b bool, def bool) string { + if b == def { + return "" + } + return fmt.Sprintf("&%s=%v", name, b) +} +func encodeInt8Param(name string, i int8, def int8) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.Itoa(int(i)) +} +func encodeInt16Param(name string, i int16, def int16) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.Itoa(int(i)) +} +func encodeInt32Param(name string, i int32, def int32) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.Itoa(int(i)) +} +func encodeInt64Param(name string, i int64, def int64) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.FormatInt(i, 10) +} +func encodeFloat32Param(name string, i float32, def float32) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.FormatFloat(float64(i), 'g', -1, 32) +} +func encodeFloat64Param(name string, i float64, def float64) string { + if i == def { + return "" + } + return "&" + name + "=" + strconv.FormatFloat(i, 'g', -1, 64) +} +func encodeOptionalEnumParam(name string, e interface{}) string { + if e == nil { + return "\"\"" + } + return fmt.Sprintf("&%s=%v", name, e) +} +func encodeOptionalBoolParam(name string, b *bool) string { + if b == nil { + return "" + } + return fmt.Sprintf("&%s=%v", name, *b) +} +func encodeOptionalInt32Param(name string, i *int32) string { + if i == nil { + return "" + } + return "&" + name + "=" + strconv.Itoa(int(*i)) +} +func encodeOptionalInt64Param(name string, i *int64) string { + if i == nil { + return "" + } + return "&" + name + "=" + strconv.Itoa(int(*i)) +} +func encodeParams(objs ...string) string { + s := strings.Join(objs, "") + if s == "" { + return s + } + return "?" + s[1:] +} + +func (client ZTSClient) GetServiceIdentity(domainName DomainName, serviceName ServiceName) (*ServiceIdentity, error) { + var data *ServiceIdentity + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/service/" + fmt.Sprint(serviceName) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) GetServiceIdentityList(domainName DomainName) (*ServiceIdentityList, error) { + var data *ServiceIdentityList + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/service" + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) GetPublicKeyEntry(domainName DomainName, serviceName SimpleName, keyId string) (*PublicKeyEntry, error) { + var data *PublicKeyEntry + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/service/" + fmt.Sprint(serviceName) + "/publickey/" + keyId + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) GetHostServices(host string) (*HostServices, error) { + var data *HostServices + url := client.URL + "/host/" + host + "/services" + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) GetDomainSignedPolicyData(domainName DomainName, matchingTag string) (*DomainSignedPolicyData, string, error) { + var data *DomainSignedPolicyData + headers := map[string]string{ + "If-None-Match": matchingTag, + } + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/signed_policy_data" + resp, err := client.httpGet(url, headers) + if err != nil { + return nil, "", err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, "", err + } + switch resp.StatusCode { + case 200, 304: + if 304 != resp.StatusCode { + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return nil, "", err + } + } + tag := resp.Header.Get(rdl.FoldHttpHeaderName("ETag")) + return data, tag, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return nil, "", errobj + } +} + +func (client ZTSClient) GetRoleToken(domainName DomainName, role EntityName, minExpiryTime *int32, maxExpiryTime *int32, proxyForPrincipal EntityName) (*RoleToken, error) { + var data *RoleToken + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/token" + encodeParams(encodeStringParam("role", string(role), ""), encodeOptionalInt32Param("minExpiryTime", minExpiryTime), encodeOptionalInt32Param("maxExpiryTime", maxExpiryTime), encodeStringParam("proxyForPrincipal", string(proxyForPrincipal), "")) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) GetAccess(domainName DomainName, roleName EntityName, principal EntityName) (*Access, error) { + var data *Access + url := client.URL + "/access/domain/" + fmt.Sprint(domainName) + "/role/" + fmt.Sprint(roleName) + "/principal/" + fmt.Sprint(principal) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) GetRoleAccess(domainName DomainName, principal EntityName) (*RoleAccess, error) { + var data *RoleAccess + url := client.URL + "/access/domain/" + fmt.Sprint(domainName) + "/principal/" + fmt.Sprint(principal) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) GetTenantDomains(providerDomainName DomainName, userName EntityName, roleName EntityName, serviceName ServiceName) (*TenantDomains, error) { + var data *TenantDomains + url := client.URL + "/providerdomain/" + fmt.Sprint(providerDomainName) + "/user/" + fmt.Sprint(userName) + encodeParams(encodeStringParam("roleName", string(roleName), ""), encodeStringParam("serviceName", string(serviceName), "")) + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) PostInstanceInformation(info *InstanceInformation) (*Identity, error) { + var data *Identity + url := client.URL + "/instance" + contentBytes, err := json.Marshal(info) + if err != nil { + return data, err + } + resp, err := client.httpPost(url, nil, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) PostInstanceRefreshRequest(domain CompoundName, service SimpleName, req *InstanceRefreshRequest) (*Identity, error) { + var data *Identity + url := client.URL + "/instance/" + fmt.Sprint(domain) + "/" + fmt.Sprint(service) + "/refresh" + contentBytes, err := json.Marshal(req) + if err != nil { + return data, err + } + resp, err := client.httpPost(url, nil, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) PostAWSInstanceInformation(info *AWSInstanceInformation) (*Identity, error) { + var data *Identity + url := client.URL + "/aws/instance" + contentBytes, err := json.Marshal(info) + if err != nil { + return data, err + } + resp, err := client.httpPost(url, nil, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) PostAWSCertificateRequest(domain CompoundName, service SimpleName, req *AWSCertificateRequest) (*Identity, error) { + var data *Identity + url := client.URL + "/aws/instance/" + fmt.Sprint(domain) + "/" + fmt.Sprint(service) + "/refresh" + contentBytes, err := json.Marshal(req) + if err != nil { + return data, err + } + resp, err := client.httpPost(url, nil, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) GetAWSTemporaryCredentials(domainName DomainName, role CompoundName) (*AWSTemporaryCredentials, error) { + var data *AWSTemporaryCredentials + url := client.URL + "/domain/" + fmt.Sprint(domainName) + "/role/" + fmt.Sprint(role) + "/creds" + resp, err := client.httpGet(url, nil) + if err != nil { + return data, err + } + contentBytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} + +func (client ZTSClient) PostDomainMetrics(domainName DomainName, req *DomainMetrics) (*DomainMetrics, error) { + var data *DomainMetrics + url := client.URL + "/metrics/" + fmt.Sprint(domainName) + contentBytes, err := json.Marshal(req) + if err != nil { + return data, err + } + resp, err := client.httpPost(url, nil, contentBytes) + if err != nil { + return data, err + } + contentBytes, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return data, err + } + switch resp.StatusCode { + case 200: + err = json.Unmarshal(contentBytes, &data) + if err != nil { + return data, err + } + return data, nil + default: + var errobj rdl.ResourceError + json.Unmarshal(contentBytes, &errobj) + if errobj.Code == 0 { + errobj.Code = resp.StatusCode + } + if errobj.Message == "" { + errobj.Message = string(contentBytes) + } + return data, errobj + } +} diff --git a/clients/go/zts/model.go b/clients/go/zts/model.go new file mode 100644 index 00000000000..efd20bca288 --- /dev/null +++ b/clients/go/zts/model.go @@ -0,0 +1,1966 @@ +// +// This file generated by rdl 1.4.8 +// + +package zts + +import ( + "encoding/json" + "fmt" + rdl "github.com/ardielle/ardielle-go/rdl" +) + +var _ = rdl.Version +var _ = json.Marshal +var _ = fmt.Printf + +// +// SimpleName - Copyright 2016 Yahoo Inc. Licensed under the terms of the +// Apache version 2.0 license. See LICENSE file for terms. Common name types +// used by several API definitions A simple identifier, an element of compound +// name. +// +type SimpleName string + +// +// CompoundName - A compound name. Most names in this API are compound names. +// +type CompoundName string + +// +// DomainName - A domain name is the general qualifier prefix, as its +// uniqueness is managed. +// +type DomainName string + +// +// EntityName - An entity name is a short form of a resource name, including +// only the domain and entity. +// +type EntityName string + +// +// ServiceName - A service name will generally be a unique subdomain. +// +type ServiceName string + +// +// LocationName - A location name is not yet defined, but will be a dotted name +// like everything else. +// +type LocationName string + +// +// ActionName - An action (operation) name. +// +type ActionName string + +// +// ResourceName - A shorthand for a YRN with no service or location. The 'tail' +// of a YRN, just the domain:entity. Note that the EntityName part is optional, +// that is, a domain name followed by a colon is valid resource name. +// +type ResourceName string + +// +// YRN - A full Yahoo Resource name (YRN). +// +type YRN string + +// +// YBase64 - The Y-specific URL-safe Base64 variant. +// +type YBase64 string + +// +// YEncoded - YEncoded includes ybase64 chars, as well as = and %. This can +// represent a user cookie and URL-encoded values. +// +type YEncoded string + +// +// AuthorityName - Used as the prefix in a signed assertion. This uniquely +// identifies a signing authority. +// +type AuthorityName string + +// +// SignedToken - A signed assertion if identity. i.e. the user cookie value. +// This token will only make sense to the authority that generated it, so it is +// beneficial to have something in the value that is cheaply recognized to +// quickly reject if it belongs to another authority. In addition to the +// YEncoded set our token includes ; to separate components and , to separate +// roles +// +type SignedToken string + +// +// PublicKeyEntry - The representation of the public key in a service identity +// object. +// +type PublicKeyEntry struct { + + // + // the public key for the service + // + Key string `json:"key"` + + // + // the key identifier (version or zone name) + // + Id string `json:"id"` +} + +// +// NewPublicKeyEntry - creates an initialized PublicKeyEntry instance, returns a pointer to it +// +func NewPublicKeyEntry(init ...*PublicKeyEntry) *PublicKeyEntry { + var o *PublicKeyEntry + if len(init) == 1 { + o = init[0] + } else { + o = new(PublicKeyEntry) + } + return o +} + +type rawPublicKeyEntry PublicKeyEntry + +// +// UnmarshalJSON is defined for proper JSON decoding of a PublicKeyEntry +// +func (pTypeDef *PublicKeyEntry) UnmarshalJSON(b []byte) error { + var r rawPublicKeyEntry + err := json.Unmarshal(b, &r) + if err == nil { + o := PublicKeyEntry(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *PublicKeyEntry) Validate() error { + if pTypeDef.Key == "" { + return fmt.Errorf("PublicKeyEntry.key is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Key) + if !val.Valid { + return fmt.Errorf("PublicKeyEntry.key does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Id == "" { + return fmt.Errorf("PublicKeyEntry.id is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Id) + if !val.Valid { + return fmt.Errorf("PublicKeyEntry.id does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// ServiceIdentity - The representation of the service identity object. +// +type ServiceIdentity struct { + + // + // the full name of the service, i.e. "sports.storage" + // + Name ServiceName `json:"name"` + + // + // array of public keys for key rotation + // + PublicKeys []*PublicKeyEntry `json:"publicKeys,omitempty" rdl:"optional"` + + // + // if present, then this service can provision tenants via this endpoint. + // + ProviderEndpoint string `json:"providerEndpoint,omitempty" rdl:"optional"` + + // + // the timestamp when this entry was last modified + // + Modified *rdl.Timestamp `json:"modified,omitempty" rdl:"optional"` + + // + // the path of the executable that runs the service + // + Executable string `json:"executable,omitempty" rdl:"optional"` + + // + // list of host names that this service can run on + // + Hosts []string `json:"hosts,omitempty" rdl:"optional"` + + // + // local (unix) user name this service can run as + // + User string `json:"user,omitempty" rdl:"optional"` + + // + // local (unix) group name this service can run as + // + Group string `json:"group,omitempty" rdl:"optional"` +} + +// +// NewServiceIdentity - creates an initialized ServiceIdentity instance, returns a pointer to it +// +func NewServiceIdentity(init ...*ServiceIdentity) *ServiceIdentity { + var o *ServiceIdentity + if len(init) == 1 { + o = init[0] + } else { + o = new(ServiceIdentity) + } + return o +} + +type rawServiceIdentity ServiceIdentity + +// +// UnmarshalJSON is defined for proper JSON decoding of a ServiceIdentity +// +func (pTypeDef *ServiceIdentity) UnmarshalJSON(b []byte) error { + var r rawServiceIdentity + err := json.Unmarshal(b, &r) + if err == nil { + o := ServiceIdentity(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *ServiceIdentity) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("ServiceIdentity.name is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "ServiceName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("ServiceIdentity.name does not contain a valid ServiceName (%v)", val.Error) + } + } + return nil +} + +// +// ServiceIdentityList - The representation for an enumeration of services in +// the namespace. +// +type ServiceIdentityList struct { + + // + // list of service names + // + Names []EntityName `json:"names"` +} + +// +// NewServiceIdentityList - creates an initialized ServiceIdentityList instance, returns a pointer to it +// +func NewServiceIdentityList(init ...*ServiceIdentityList) *ServiceIdentityList { + var o *ServiceIdentityList + if len(init) == 1 { + o = init[0] + } else { + o = new(ServiceIdentityList) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *ServiceIdentityList) Init() *ServiceIdentityList { + if pTypeDef.Names == nil { + pTypeDef.Names = make([]EntityName, 0) + } + return pTypeDef +} + +type rawServiceIdentityList ServiceIdentityList + +// +// UnmarshalJSON is defined for proper JSON decoding of a ServiceIdentityList +// +func (pTypeDef *ServiceIdentityList) UnmarshalJSON(b []byte) error { + var r rawServiceIdentityList + err := json.Unmarshal(b, &r) + if err == nil { + o := ServiceIdentityList(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *ServiceIdentityList) Validate() error { + if pTypeDef.Names == nil { + return fmt.Errorf("ServiceIdentityList: Missing required field: names") + } + return nil +} + +// +// HostServices - The representation for an enumeration of services authorized +// to run on a specific host. +// +type HostServices struct { + + // + // name of the host + // + Host string `json:"host"` + + // + // list of service names authorized to run on this host + // + Names []EntityName `json:"names"` +} + +// +// NewHostServices - creates an initialized HostServices instance, returns a pointer to it +// +func NewHostServices(init ...*HostServices) *HostServices { + var o *HostServices + if len(init) == 1 { + o = init[0] + } else { + o = new(HostServices) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *HostServices) Init() *HostServices { + if pTypeDef.Names == nil { + pTypeDef.Names = make([]EntityName, 0) + } + return pTypeDef +} + +type rawHostServices HostServices + +// +// UnmarshalJSON is defined for proper JSON decoding of a HostServices +// +func (pTypeDef *HostServices) UnmarshalJSON(b []byte) error { + var r rawHostServices + err := json.Unmarshal(b, &r) + if err == nil { + o := HostServices(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *HostServices) Validate() error { + if pTypeDef.Host == "" { + return fmt.Errorf("HostServices.host is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Host) + if !val.Valid { + return fmt.Errorf("HostServices.host does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Names == nil { + return fmt.Errorf("HostServices: Missing required field: names") + } + return nil +} + +// +// AssertionEffect - Every assertion can have the effect of ALLOW or DENY. +// +type AssertionEffect int + +// +// AssertionEffect constants +// +const ( + _ AssertionEffect = iota + ALLOW + DENY +) + +var namesAssertionEffect = []string{ + ALLOW: "ALLOW", + DENY: "DENY", +} + +// +// NewAssertionEffect - return a string representation of the enum +// +func NewAssertionEffect(init ...interface{}) AssertionEffect { + if len(init) == 1 { + switch v := init[0].(type) { + case AssertionEffect: + return v + case int: + return AssertionEffect(v) + case int32: + return AssertionEffect(v) + case string: + for i, s := range namesAssertionEffect { + if s == v { + return AssertionEffect(i) + } + } + default: + panic("Bad init value for AssertionEffect enum") + } + } + return AssertionEffect(0) //default to the first enum value +} + +// +// String - return a string representation of the enum +// +func (e AssertionEffect) String() string { + return namesAssertionEffect[e] +} + +// +// SymbolSet - return an array of all valid string representations (symbols) of the enum +// +func (e AssertionEffect) SymbolSet() []string { + return namesAssertionEffect +} + +// +// MarshalJSON is defined for proper JSON encoding of a AssertionEffect +// +func (e AssertionEffect) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +// +// UnmarshalJSON is defined for proper JSON decoding of a AssertionEffect +// +func (e *AssertionEffect) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err == nil { + s := string(j) + for v, s2 := range namesAssertionEffect { + if s == s2 { + *e = AssertionEffect(v) + return nil + } + } + err = fmt.Errorf("Bad enum symbol for type AssertionEffect: %s", s) + } + return err +} + +// +// Assertion - A representation for the encapsulation of an action to be +// performed on a resource by a principal. +// +type Assertion struct { + + // + // the subject of the assertion, a role + // + Role string `json:"role"` + + // + // the object of the assertion. Must be in the local namespace. Can contain + // wildcards + // + Resource string `json:"resource"` + + // + // the predicate of the assertion. Can contain wildcards + // + Action string `json:"action"` + + // + // the effect of the assertion in the policy language + // + Effect *AssertionEffect `json:"effect,omitempty" rdl:"optional"` + + // + // assertion id - auto generated by server + // + Id *int64 `json:"id,omitempty" rdl:"optional"` +} + +// +// NewAssertion - creates an initialized Assertion instance, returns a pointer to it +// +func NewAssertion(init ...*Assertion) *Assertion { + var o *Assertion + if len(init) == 1 { + o = init[0] + } else { + o = new(Assertion) + } + return o +} + +type rawAssertion Assertion + +// +// UnmarshalJSON is defined for proper JSON decoding of a Assertion +// +func (pTypeDef *Assertion) UnmarshalJSON(b []byte) error { + var r rawAssertion + err := json.Unmarshal(b, &r) + if err == nil { + o := Assertion(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Assertion) Validate() error { + if pTypeDef.Role == "" { + return fmt.Errorf("Assertion.role is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Role) + if !val.Valid { + return fmt.Errorf("Assertion.role does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Resource == "" { + return fmt.Errorf("Assertion.resource is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Resource) + if !val.Valid { + return fmt.Errorf("Assertion.resource does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Action == "" { + return fmt.Errorf("Assertion.action is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Action) + if !val.Valid { + return fmt.Errorf("Assertion.action does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// Policy - The representation for a Policy with set of assertions. +// +type Policy struct { + + // + // name of the policy + // + Name ResourceName `json:"name"` + + // + // last modification timestamp of this policy + // + Modified *rdl.Timestamp `json:"modified,omitempty" rdl:"optional"` + + // + // list of defined assertions for this policy + // + Assertions []*Assertion `json:"assertions"` +} + +// +// NewPolicy - creates an initialized Policy instance, returns a pointer to it +// +func NewPolicy(init ...*Policy) *Policy { + var o *Policy + if len(init) == 1 { + o = init[0] + } else { + o = new(Policy) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *Policy) Init() *Policy { + if pTypeDef.Assertions == nil { + pTypeDef.Assertions = make([]*Assertion, 0) + } + return pTypeDef +} + +type rawPolicy Policy + +// +// UnmarshalJSON is defined for proper JSON decoding of a Policy +// +func (pTypeDef *Policy) UnmarshalJSON(b []byte) error { + var r rawPolicy + err := json.Unmarshal(b, &r) + if err == nil { + o := Policy(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Policy) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("Policy.name is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "ResourceName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("Policy.name does not contain a valid ResourceName (%v)", val.Error) + } + } + if pTypeDef.Assertions == nil { + return fmt.Errorf("Policy: Missing required field: assertions") + } + return nil +} + +// +// PolicyData - +// +type PolicyData struct { + + // + // name of the domain + // + Domain DomainName `json:"domain"` + + // + // list of policies defined in this server + // + Policies []*Policy `json:"policies"` +} + +// +// NewPolicyData - creates an initialized PolicyData instance, returns a pointer to it +// +func NewPolicyData(init ...*PolicyData) *PolicyData { + var o *PolicyData + if len(init) == 1 { + o = init[0] + } else { + o = new(PolicyData) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *PolicyData) Init() *PolicyData { + if pTypeDef.Policies == nil { + pTypeDef.Policies = make([]*Policy, 0) + } + return pTypeDef +} + +type rawPolicyData PolicyData + +// +// UnmarshalJSON is defined for proper JSON decoding of a PolicyData +// +func (pTypeDef *PolicyData) UnmarshalJSON(b []byte) error { + var r rawPolicyData + err := json.Unmarshal(b, &r) + if err == nil { + o := PolicyData(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *PolicyData) Validate() error { + if pTypeDef.Domain == "" { + return fmt.Errorf("PolicyData.domain is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "DomainName", pTypeDef.Domain) + if !val.Valid { + return fmt.Errorf("PolicyData.domain does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.Policies == nil { + return fmt.Errorf("PolicyData: Missing required field: policies") + } + return nil +} + +// +// SignedPolicyData - A representation of policies object defined in a given +// server. +// +type SignedPolicyData struct { + + // + // list of policies defined in a domain + // + PolicyData *PolicyData `json:"policyData"` + + // + // zms signature generated based on the domain policies object + // + ZmsSignature string `json:"zmsSignature"` + + // + // the identifier of the zms key used to generate the signature + // + ZmsKeyId string `json:"zmsKeyId"` + + // + // when the domain itself was last modified + // + Modified rdl.Timestamp `json:"modified"` + + // + // timestamp specifying the expiration time for using this set of policies + // + Expires rdl.Timestamp `json:"expires"` +} + +// +// NewSignedPolicyData - creates an initialized SignedPolicyData instance, returns a pointer to it +// +func NewSignedPolicyData(init ...*SignedPolicyData) *SignedPolicyData { + var o *SignedPolicyData + if len(init) == 1 { + o = init[0] + } else { + o = new(SignedPolicyData) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *SignedPolicyData) Init() *SignedPolicyData { + if pTypeDef.PolicyData == nil { + pTypeDef.PolicyData = NewPolicyData() + } + return pTypeDef +} + +type rawSignedPolicyData SignedPolicyData + +// +// UnmarshalJSON is defined for proper JSON decoding of a SignedPolicyData +// +func (pTypeDef *SignedPolicyData) UnmarshalJSON(b []byte) error { + var r rawSignedPolicyData + err := json.Unmarshal(b, &r) + if err == nil { + o := SignedPolicyData(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *SignedPolicyData) Validate() error { + if pTypeDef.PolicyData == nil { + return fmt.Errorf("SignedPolicyData: Missing required field: policyData") + } + if pTypeDef.ZmsSignature == "" { + return fmt.Errorf("SignedPolicyData.zmsSignature is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.ZmsSignature) + if !val.Valid { + return fmt.Errorf("SignedPolicyData.zmsSignature does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.ZmsKeyId == "" { + return fmt.Errorf("SignedPolicyData.zmsKeyId is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.ZmsKeyId) + if !val.Valid { + return fmt.Errorf("SignedPolicyData.zmsKeyId does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Modified.IsZero() { + return fmt.Errorf("SignedPolicyData: Missing required field: modified") + } + if pTypeDef.Expires.IsZero() { + return fmt.Errorf("SignedPolicyData: Missing required field: expires") + } + return nil +} + +// +// DomainSignedPolicyData - A signed bulk transfer of policies. The data is +// signed with server's private key. +// +type DomainSignedPolicyData struct { + + // + // policy data signed by ZMS + // + SignedPolicyData *SignedPolicyData `json:"signedPolicyData"` + + // + // signature generated based on the domain policies object + // + Signature string `json:"signature"` + + // + // the identifier of the key used to generate the signature + // + KeyId string `json:"keyId"` +} + +// +// NewDomainSignedPolicyData - creates an initialized DomainSignedPolicyData instance, returns a pointer to it +// +func NewDomainSignedPolicyData(init ...*DomainSignedPolicyData) *DomainSignedPolicyData { + var o *DomainSignedPolicyData + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainSignedPolicyData) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *DomainSignedPolicyData) Init() *DomainSignedPolicyData { + if pTypeDef.SignedPolicyData == nil { + pTypeDef.SignedPolicyData = NewSignedPolicyData() + } + return pTypeDef +} + +type rawDomainSignedPolicyData DomainSignedPolicyData + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainSignedPolicyData +// +func (pTypeDef *DomainSignedPolicyData) UnmarshalJSON(b []byte) error { + var r rawDomainSignedPolicyData + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainSignedPolicyData(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainSignedPolicyData) Validate() error { + if pTypeDef.SignedPolicyData == nil { + return fmt.Errorf("DomainSignedPolicyData: Missing required field: signedPolicyData") + } + if pTypeDef.Signature == "" { + return fmt.Errorf("DomainSignedPolicyData.signature is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Signature) + if !val.Valid { + return fmt.Errorf("DomainSignedPolicyData.signature does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.KeyId == "" { + return fmt.Errorf("DomainSignedPolicyData.keyId is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.KeyId) + if !val.Valid { + return fmt.Errorf("DomainSignedPolicyData.keyId does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// RoleToken - A representation of a signed RoleToken +// +type RoleToken struct { + Token string `json:"token"` + ExpiryTime int64 `json:"expiryTime"` +} + +// +// NewRoleToken - creates an initialized RoleToken instance, returns a pointer to it +// +func NewRoleToken(init ...*RoleToken) *RoleToken { + var o *RoleToken + if len(init) == 1 { + o = init[0] + } else { + o = new(RoleToken) + } + return o +} + +type rawRoleToken RoleToken + +// +// UnmarshalJSON is defined for proper JSON decoding of a RoleToken +// +func (pTypeDef *RoleToken) UnmarshalJSON(b []byte) error { + var r rawRoleToken + err := json.Unmarshal(b, &r) + if err == nil { + o := RoleToken(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *RoleToken) Validate() error { + if pTypeDef.Token == "" { + return fmt.Errorf("RoleToken.token is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Token) + if !val.Valid { + return fmt.Errorf("RoleToken.token does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// Access - Access can be checked and returned as this resource. +// +type Access struct { + + // + // true (allowed) or false (denied) + // + Granted bool `json:"granted"` +} + +// +// NewAccess - creates an initialized Access instance, returns a pointer to it +// +func NewAccess(init ...*Access) *Access { + var o *Access + if len(init) == 1 { + o = init[0] + } else { + o = new(Access) + } + return o +} + +type rawAccess Access + +// +// UnmarshalJSON is defined for proper JSON decoding of a Access +// +func (pTypeDef *Access) UnmarshalJSON(b []byte) error { + var r rawAccess + err := json.Unmarshal(b, &r) + if err == nil { + o := Access(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Access) Validate() error { + return nil +} + +// +// RoleAccess - +// +type RoleAccess struct { + Roles []EntityName `json:"roles"` +} + +// +// NewRoleAccess - creates an initialized RoleAccess instance, returns a pointer to it +// +func NewRoleAccess(init ...*RoleAccess) *RoleAccess { + var o *RoleAccess + if len(init) == 1 { + o = init[0] + } else { + o = new(RoleAccess) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *RoleAccess) Init() *RoleAccess { + if pTypeDef.Roles == nil { + pTypeDef.Roles = make([]EntityName, 0) + } + return pTypeDef +} + +type rawRoleAccess RoleAccess + +// +// UnmarshalJSON is defined for proper JSON decoding of a RoleAccess +// +func (pTypeDef *RoleAccess) UnmarshalJSON(b []byte) error { + var r rawRoleAccess + err := json.Unmarshal(b, &r) + if err == nil { + o := RoleAccess(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *RoleAccess) Validate() error { + if pTypeDef.Roles == nil { + return fmt.Errorf("RoleAccess: Missing required field: roles") + } + return nil +} + +// +// TenantDomains - +// +type TenantDomains struct { + TenantDomainNames []DomainName `json:"tenantDomainNames"` +} + +// +// NewTenantDomains - creates an initialized TenantDomains instance, returns a pointer to it +// +func NewTenantDomains(init ...*TenantDomains) *TenantDomains { + var o *TenantDomains + if len(init) == 1 { + o = init[0] + } else { + o = new(TenantDomains) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *TenantDomains) Init() *TenantDomains { + if pTypeDef.TenantDomainNames == nil { + pTypeDef.TenantDomainNames = make([]DomainName, 0) + } + return pTypeDef +} + +type rawTenantDomains TenantDomains + +// +// UnmarshalJSON is defined for proper JSON decoding of a TenantDomains +// +func (pTypeDef *TenantDomains) UnmarshalJSON(b []byte) error { + var r rawTenantDomains + err := json.Unmarshal(b, &r) + if err == nil { + o := TenantDomains(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *TenantDomains) Validate() error { + if pTypeDef.TenantDomainNames == nil { + return fmt.Errorf("TenantDomains: Missing required field: tenantDomainNames") + } + return nil +} + +// +// Identity - Identity - a signed assertion of service or human identity, the +// response could be either a client certificate or just a regular NToken +// (depending if the request contained a csr or not). +// +type Identity struct { + + // + // name of the identity, fully qualified, i.e. my.domain.service1, or + // aws.1232321321312.myusername + // + Name CompoundName `json:"name"` + + // + // a certificate usable for both client and server in TLS connections + // + Certificate string `json:"certificate,omitempty" rdl:"optional"` + + // + // the CA certificate chain to use with all IMS-generated certs + // + CaCertBundle string `json:"caCertBundle,omitempty" rdl:"optional"` + + // + // the SSH server cert, signed by the CA + // + SshServerCert string `json:"sshServerCert,omitempty" rdl:"optional"` + + // + // service token instead of TLS certificate + // + ServiceToken SignedToken `json:"serviceToken,omitempty" rdl:"optional"` + + // + // other config-like attributes determined at boot time + // + Attributes map[string]string `json:"attributes,omitempty" rdl:"optional"` +} + +// +// NewIdentity - creates an initialized Identity instance, returns a pointer to it +// +func NewIdentity(init ...*Identity) *Identity { + var o *Identity + if len(init) == 1 { + o = init[0] + } else { + o = new(Identity) + } + return o +} + +type rawIdentity Identity + +// +// UnmarshalJSON is defined for proper JSON decoding of a Identity +// +func (pTypeDef *Identity) UnmarshalJSON(b []byte) error { + var r rawIdentity + err := json.Unmarshal(b, &r) + if err == nil { + o := Identity(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *Identity) Validate() error { + if pTypeDef.Name == "" { + return fmt.Errorf("Identity.name is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "CompoundName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("Identity.name does not contain a valid CompoundName (%v)", val.Error) + } + } + return nil +} + +// +// InstanceInformation - Instance object that includes requested service +// details plus host document that is signed by provider as part of the host +// bootstrap process +// +type InstanceInformation struct { + + // + // signed document containing attributes like IP address, instance-id, + // account#, etc. + // + Document string `json:"document"` + + // + // the signature for the document + // + Signature string `json:"signature"` + + // + // the keyid used to sign the document + // + KeyId string `json:"keyId"` + + // + // the domain of the instance + // + Domain CompoundName `json:"domain"` + + // + // the service this instance is supposed to run + // + Service SimpleName `json:"service"` + + // + // return a certificate in the response + // + Csr string `json:"csr"` +} + +// +// NewInstanceInformation - creates an initialized InstanceInformation instance, returns a pointer to it +// +func NewInstanceInformation(init ...*InstanceInformation) *InstanceInformation { + var o *InstanceInformation + if len(init) == 1 { + o = init[0] + } else { + o = new(InstanceInformation) + } + return o +} + +type rawInstanceInformation InstanceInformation + +// +// UnmarshalJSON is defined for proper JSON decoding of a InstanceInformation +// +func (pTypeDef *InstanceInformation) UnmarshalJSON(b []byte) error { + var r rawInstanceInformation + err := json.Unmarshal(b, &r) + if err == nil { + o := InstanceInformation(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *InstanceInformation) Validate() error { + if pTypeDef.Document == "" { + return fmt.Errorf("InstanceInformation.document is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Document) + if !val.Valid { + return fmt.Errorf("InstanceInformation.document does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Signature == "" { + return fmt.Errorf("InstanceInformation.signature is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Signature) + if !val.Valid { + return fmt.Errorf("InstanceInformation.signature does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.KeyId == "" { + return fmt.Errorf("InstanceInformation.keyId is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.KeyId) + if !val.Valid { + return fmt.Errorf("InstanceInformation.keyId does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Domain == "" { + return fmt.Errorf("InstanceInformation.domain is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "CompoundName", pTypeDef.Domain) + if !val.Valid { + return fmt.Errorf("InstanceInformation.domain does not contain a valid CompoundName (%v)", val.Error) + } + } + if pTypeDef.Service == "" { + return fmt.Errorf("InstanceInformation.service is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "SimpleName", pTypeDef.Service) + if !val.Valid { + return fmt.Errorf("InstanceInformation.service does not contain a valid SimpleName (%v)", val.Error) + } + } + if pTypeDef.Csr == "" { + return fmt.Errorf("InstanceInformation.csr is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Csr) + if !val.Valid { + return fmt.Errorf("InstanceInformation.csr does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// InstanceRefreshRequest - InstanceRefreshRequest - a certificate refresh +// request +// +type InstanceRefreshRequest struct { + + // + // Cert CSR if requesting TLS certificate + // + Csr string `json:"csr"` + + // + // in seconds how long token should be valid for + // + ExpiryTime *int32 `json:"expiryTime,omitempty" rdl:"optional"` +} + +// +// NewInstanceRefreshRequest - creates an initialized InstanceRefreshRequest instance, returns a pointer to it +// +func NewInstanceRefreshRequest(init ...*InstanceRefreshRequest) *InstanceRefreshRequest { + var o *InstanceRefreshRequest + if len(init) == 1 { + o = init[0] + } else { + o = new(InstanceRefreshRequest) + } + return o +} + +type rawInstanceRefreshRequest InstanceRefreshRequest + +// +// UnmarshalJSON is defined for proper JSON decoding of a InstanceRefreshRequest +// +func (pTypeDef *InstanceRefreshRequest) UnmarshalJSON(b []byte) error { + var r rawInstanceRefreshRequest + err := json.Unmarshal(b, &r) + if err == nil { + o := InstanceRefreshRequest(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *InstanceRefreshRequest) Validate() error { + if pTypeDef.Csr == "" { + return fmt.Errorf("InstanceRefreshRequest.csr is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Csr) + if !val.Valid { + return fmt.Errorf("InstanceRefreshRequest.csr does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// AWSInstanceInformation - AWSInstanceInformation - the information a booting +// EC2 instance must provide to ZTS to authenticate. +// +type AWSInstanceInformation struct { + + // + // signed document containing attributes like IP address, instance-id, + // account#, etc. + // + Document string `json:"document"` + + // + // the signature for the document + // + Signature string `json:"signature"` + + // + // the domain of the instance + // + Domain CompoundName `json:"domain"` + + // + // the service this instance is supposed to run + // + Service SimpleName `json:"service"` + + // + // return a certificate in the response + // + Csr string `json:"csr"` + + // + // the full service identity name (same as the EC2 instance profile name) + // + Name CompoundName `json:"name"` + + // + // the account id (as a string) for the instance. parsed from the instance + // profile ARN + // + Account SimpleName `json:"account"` + + // + // the name of the cloud (namespace) within the account, parsed from the name + // + Cloud SimpleName `json:"cloud,omitempty" rdl:"optional"` + + // + // the name of the subnet this instance is expected to be running in, parsed + // from the name + // + Subnet SimpleName `json:"subnet"` + + // + // the AWS Access Key Id for the role + // + Access string `json:"access"` + + // + // the AWS Secret Access Key for the role + // + Secret string `json:"secret"` + + // + // the AWS STS Token for the role + // + Token string `json:"token"` + + // + // the expiration time of the access keys + // + Expires rdl.Timestamp `json:"expires"` + + // + // the modified time of the access keys + // + Modified rdl.Timestamp `json:"modified"` + + // + // the 'flavor' of the access keys, i.e. "AWS-HMAC" + // + Flavor string `json:"flavor"` +} + +// +// NewAWSInstanceInformation - creates an initialized AWSInstanceInformation instance, returns a pointer to it +// +func NewAWSInstanceInformation(init ...*AWSInstanceInformation) *AWSInstanceInformation { + var o *AWSInstanceInformation + if len(init) == 1 { + o = init[0] + } else { + o = new(AWSInstanceInformation) + } + return o +} + +type rawAWSInstanceInformation AWSInstanceInformation + +// +// UnmarshalJSON is defined for proper JSON decoding of a AWSInstanceInformation +// +func (pTypeDef *AWSInstanceInformation) UnmarshalJSON(b []byte) error { + var r rawAWSInstanceInformation + err := json.Unmarshal(b, &r) + if err == nil { + o := AWSInstanceInformation(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *AWSInstanceInformation) Validate() error { + if pTypeDef.Document == "" { + return fmt.Errorf("AWSInstanceInformation.document is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Document) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.document does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Signature == "" { + return fmt.Errorf("AWSInstanceInformation.signature is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Signature) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.signature does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Domain == "" { + return fmt.Errorf("AWSInstanceInformation.domain is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "CompoundName", pTypeDef.Domain) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.domain does not contain a valid CompoundName (%v)", val.Error) + } + } + if pTypeDef.Service == "" { + return fmt.Errorf("AWSInstanceInformation.service is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "SimpleName", pTypeDef.Service) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.service does not contain a valid SimpleName (%v)", val.Error) + } + } + if pTypeDef.Csr == "" { + return fmt.Errorf("AWSInstanceInformation.csr is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Csr) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.csr does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Name == "" { + return fmt.Errorf("AWSInstanceInformation.name is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "CompoundName", pTypeDef.Name) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.name does not contain a valid CompoundName (%v)", val.Error) + } + } + if pTypeDef.Account == "" { + return fmt.Errorf("AWSInstanceInformation.account is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "SimpleName", pTypeDef.Account) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.account does not contain a valid SimpleName (%v)", val.Error) + } + } + if pTypeDef.Subnet == "" { + return fmt.Errorf("AWSInstanceInformation.subnet is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "SimpleName", pTypeDef.Subnet) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.subnet does not contain a valid SimpleName (%v)", val.Error) + } + } + if pTypeDef.Access == "" { + return fmt.Errorf("AWSInstanceInformation.access is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Access) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.access does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Secret == "" { + return fmt.Errorf("AWSInstanceInformation.secret is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Secret) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.secret does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Token == "" { + return fmt.Errorf("AWSInstanceInformation.token is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Token) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.token does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Expires.IsZero() { + return fmt.Errorf("AWSInstanceInformation: Missing required field: expires") + } + if pTypeDef.Modified.IsZero() { + return fmt.Errorf("AWSInstanceInformation: Missing required field: modified") + } + if pTypeDef.Flavor == "" { + return fmt.Errorf("AWSInstanceInformation.flavor is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Flavor) + if !val.Valid { + return fmt.Errorf("AWSInstanceInformation.flavor does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// AWSCertificateRequest - AWSCertificateRequest - a certificate signing +// request +// +type AWSCertificateRequest struct { + Csr string `json:"csr"` +} + +// +// NewAWSCertificateRequest - creates an initialized AWSCertificateRequest instance, returns a pointer to it +// +func NewAWSCertificateRequest(init ...*AWSCertificateRequest) *AWSCertificateRequest { + var o *AWSCertificateRequest + if len(init) == 1 { + o = init[0] + } else { + o = new(AWSCertificateRequest) + } + return o +} + +type rawAWSCertificateRequest AWSCertificateRequest + +// +// UnmarshalJSON is defined for proper JSON decoding of a AWSCertificateRequest +// +func (pTypeDef *AWSCertificateRequest) UnmarshalJSON(b []byte) error { + var r rawAWSCertificateRequest + err := json.Unmarshal(b, &r) + if err == nil { + o := AWSCertificateRequest(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *AWSCertificateRequest) Validate() error { + if pTypeDef.Csr == "" { + return fmt.Errorf("AWSCertificateRequest.csr is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.Csr) + if !val.Valid { + return fmt.Errorf("AWSCertificateRequest.csr does not contain a valid String (%v)", val.Error) + } + } + return nil +} + +// +// AWSTemporaryCredentials - +// +type AWSTemporaryCredentials struct { + AccessKeyId string `json:"accessKeyId"` + SecretAccessKey string `json:"secretAccessKey"` + SessionToken string `json:"sessionToken"` + Expiration rdl.Timestamp `json:"expiration"` +} + +// +// NewAWSTemporaryCredentials - creates an initialized AWSTemporaryCredentials instance, returns a pointer to it +// +func NewAWSTemporaryCredentials(init ...*AWSTemporaryCredentials) *AWSTemporaryCredentials { + var o *AWSTemporaryCredentials + if len(init) == 1 { + o = init[0] + } else { + o = new(AWSTemporaryCredentials) + } + return o +} + +type rawAWSTemporaryCredentials AWSTemporaryCredentials + +// +// UnmarshalJSON is defined for proper JSON decoding of a AWSTemporaryCredentials +// +func (pTypeDef *AWSTemporaryCredentials) UnmarshalJSON(b []byte) error { + var r rawAWSTemporaryCredentials + err := json.Unmarshal(b, &r) + if err == nil { + o := AWSTemporaryCredentials(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *AWSTemporaryCredentials) Validate() error { + if pTypeDef.AccessKeyId == "" { + return fmt.Errorf("AWSTemporaryCredentials.accessKeyId is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.AccessKeyId) + if !val.Valid { + return fmt.Errorf("AWSTemporaryCredentials.accessKeyId does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.SecretAccessKey == "" { + return fmt.Errorf("AWSTemporaryCredentials.secretAccessKey is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.SecretAccessKey) + if !val.Valid { + return fmt.Errorf("AWSTemporaryCredentials.secretAccessKey does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.SessionToken == "" { + return fmt.Errorf("AWSTemporaryCredentials.sessionToken is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "String", pTypeDef.SessionToken) + if !val.Valid { + return fmt.Errorf("AWSTemporaryCredentials.sessionToken does not contain a valid String (%v)", val.Error) + } + } + if pTypeDef.Expiration.IsZero() { + return fmt.Errorf("AWSTemporaryCredentials: Missing required field: expiration") + } + return nil +} + +// +// DomainMetricType - zpe metric attributes +// +type DomainMetricType int + +// +// DomainMetricType constants +// +const ( + _ DomainMetricType = iota + ACCESS_ALLOWED + ACCESS_ALLOWED_DENY + ACCESS_ALLOWED_DENY_NO_MATCH + ACCESS_ALLOWED_ALLOW + ACCESS_ALLOWED_ERROR + ACCESS_ALLOWED_TOKEN_INVALID + ACCESS_Allowed_TOKEN_EXPIRED + ACCESS_ALLOWED_DOMAIN_NOT_FOUND + ACCESS_ALLOWED_DOMAIN_MISMATCH + ACCESS_ALLOWED_DOMAIN_EXPIRED + ACCESS_ALLOWED_DOMAIN_EMPTY + ACCESS_ALLOWED_TOKEN_CACHE_FAILURE + ACCESS_ALLOWED_TOKEN_CACHE_NOT_FOUND + ACCESS_ALLOWED_TOKEN_CACHE_SUCCESS + ACCESS_ALLOWED_TOKEN_VALIDATE + LOAD_FILE_FAIL + LOAD_FILE_GOOD + LOAD_DOMAIN_GOOD +) + +var namesDomainMetricType = []string{ + ACCESS_ALLOWED: "ACCESS_ALLOWED", + ACCESS_ALLOWED_DENY: "ACCESS_ALLOWED_DENY", + ACCESS_ALLOWED_DENY_NO_MATCH: "ACCESS_ALLOWED_DENY_NO_MATCH", + ACCESS_ALLOWED_ALLOW: "ACCESS_ALLOWED_ALLOW", + ACCESS_ALLOWED_ERROR: "ACCESS_ALLOWED_ERROR", + ACCESS_ALLOWED_TOKEN_INVALID: "ACCESS_ALLOWED_TOKEN_INVALID", + ACCESS_Allowed_TOKEN_EXPIRED: "ACCESS_Allowed_TOKEN_EXPIRED", + ACCESS_ALLOWED_DOMAIN_NOT_FOUND: "ACCESS_ALLOWED_DOMAIN_NOT_FOUND", + ACCESS_ALLOWED_DOMAIN_MISMATCH: "ACCESS_ALLOWED_DOMAIN_MISMATCH", + ACCESS_ALLOWED_DOMAIN_EXPIRED: "ACCESS_ALLOWED_DOMAIN_EXPIRED", + ACCESS_ALLOWED_DOMAIN_EMPTY: "ACCESS_ALLOWED_DOMAIN_EMPTY", + ACCESS_ALLOWED_TOKEN_CACHE_FAILURE: "ACCESS_ALLOWED_TOKEN_CACHE_FAILURE", + ACCESS_ALLOWED_TOKEN_CACHE_NOT_FOUND: "ACCESS_ALLOWED_TOKEN_CACHE_NOT_FOUND", + ACCESS_ALLOWED_TOKEN_CACHE_SUCCESS: "ACCESS_ALLOWED_TOKEN_CACHE_SUCCESS", + ACCESS_ALLOWED_TOKEN_VALIDATE: "ACCESS_ALLOWED_TOKEN_VALIDATE", + LOAD_FILE_FAIL: "LOAD_FILE_FAIL", + LOAD_FILE_GOOD: "LOAD_FILE_GOOD", + LOAD_DOMAIN_GOOD: "LOAD_DOMAIN_GOOD", +} + +// +// NewDomainMetricType - return a string representation of the enum +// +func NewDomainMetricType(init ...interface{}) DomainMetricType { + if len(init) == 1 { + switch v := init[0].(type) { + case DomainMetricType: + return v + case int: + return DomainMetricType(v) + case int32: + return DomainMetricType(v) + case string: + for i, s := range namesDomainMetricType { + if s == v { + return DomainMetricType(i) + } + } + default: + panic("Bad init value for DomainMetricType enum") + } + } + return DomainMetricType(0) //default to the first enum value +} + +// +// String - return a string representation of the enum +// +func (e DomainMetricType) String() string { + return namesDomainMetricType[e] +} + +// +// SymbolSet - return an array of all valid string representations (symbols) of the enum +// +func (e DomainMetricType) SymbolSet() []string { + return namesDomainMetricType +} + +// +// MarshalJSON is defined for proper JSON encoding of a DomainMetricType +// +func (e DomainMetricType) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainMetricType +// +func (e *DomainMetricType) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err == nil { + s := string(j) + for v, s2 := range namesDomainMetricType { + if s == s2 { + *e = DomainMetricType(v) + return nil + } + } + err = fmt.Errorf("Bad enum symbol for type DomainMetricType: %s", s) + } + return err +} + +// +// DomainMetric - +// +type DomainMetric struct { + MetricType DomainMetricType `json:"metricType"` + MetricVal int32 `json:"metricVal"` +} + +// +// NewDomainMetric - creates an initialized DomainMetric instance, returns a pointer to it +// +func NewDomainMetric(init ...*DomainMetric) *DomainMetric { + var o *DomainMetric + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainMetric) + } + return o +} + +type rawDomainMetric DomainMetric + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainMetric +// +func (pTypeDef *DomainMetric) UnmarshalJSON(b []byte) error { + var r rawDomainMetric + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainMetric(r) + *pTypeDef = o + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainMetric) Validate() error { + return nil +} + +// +// DomainMetrics - +// +type DomainMetrics struct { + + // + // name of the domain the metrics pertain to + // + DomainName DomainName `json:"domainName"` + + // + // list of the domains metrics + // + MetricList []*DomainMetric `json:"metricList"` +} + +// +// NewDomainMetrics - creates an initialized DomainMetrics instance, returns a pointer to it +// +func NewDomainMetrics(init ...*DomainMetrics) *DomainMetrics { + var o *DomainMetrics + if len(init) == 1 { + o = init[0] + } else { + o = new(DomainMetrics) + } + return o.Init() +} + +// +// Init - sets up the instance according to its default field values, if any +// +func (pTypeDef *DomainMetrics) Init() *DomainMetrics { + if pTypeDef.MetricList == nil { + pTypeDef.MetricList = make([]*DomainMetric, 0) + } + return pTypeDef +} + +type rawDomainMetrics DomainMetrics + +// +// UnmarshalJSON is defined for proper JSON decoding of a DomainMetrics +// +func (pTypeDef *DomainMetrics) UnmarshalJSON(b []byte) error { + var r rawDomainMetrics + err := json.Unmarshal(b, &r) + if err == nil { + o := DomainMetrics(r) + *pTypeDef = *((&o).Init()) + err = pTypeDef.Validate() + } + return err +} + +// +// Validate - checks for missing required fields, etc +// +func (pTypeDef *DomainMetrics) Validate() error { + if pTypeDef.DomainName == "" { + return fmt.Errorf("DomainMetrics.domainName is missing but is a required field") + } else { + val := rdl.Validate(ZTSSchema(), "DomainName", pTypeDef.DomainName) + if !val.Valid { + return fmt.Errorf("DomainMetrics.domainName does not contain a valid DomainName (%v)", val.Error) + } + } + if pTypeDef.MetricList == nil { + return fmt.Errorf("DomainMetrics: Missing required field: metricList") + } + return nil +} diff --git a/clients/go/zts/zts_schema.go b/clients/go/zts/zts_schema.go new file mode 100644 index 00000000000..8d1648ba866 --- /dev/null +++ b/clients/go/zts/zts_schema.go @@ -0,0 +1,418 @@ +// +// This file generated by rdl 1.4.8 +// + +package zts + +import ( + rdl "github.com/ardielle/ardielle-go/rdl" +) + +var schema *rdl.Schema + +func init() { + sb := rdl.NewSchemaBuilder("ZTS") + sb.Version(1) + sb.Namespace("com.yahoo.athenz.zts") + sb.Comment("Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. The Authorization Management Service (ZTS) API") + + tSimpleName := rdl.NewStringTypeBuilder("SimpleName") + tSimpleName.Comment("Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. Common name types used by several API definitions A simple identifier, an element of compound name.") + tSimpleName.Pattern("[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tSimpleName.Build()) + + tCompoundName := rdl.NewStringTypeBuilder("CompoundName") + tCompoundName.Comment("A compound name. Most names in this API are compound names.") + tCompoundName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tCompoundName.Build()) + + tDomainName := rdl.NewStringTypeBuilder("DomainName") + tDomainName.Comment("A domain name is the general qualifier prefix, as its uniqueness is managed.") + tDomainName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tDomainName.Build()) + + tEntityName := rdl.NewStringTypeBuilder("EntityName") + tEntityName.Comment("An entity name is a short form of a resource name, including only the domain and entity.") + tEntityName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tEntityName.Build()) + + tServiceName := rdl.NewStringTypeBuilder("ServiceName") + tServiceName.Comment("A service name will generally be a unique subdomain.") + tServiceName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tServiceName.Build()) + + tLocationName := rdl.NewStringTypeBuilder("LocationName") + tLocationName.Comment("A location name is not yet defined, but will be a dotted name like everything else.") + tLocationName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tLocationName.Build()) + + tActionName := rdl.NewStringTypeBuilder("ActionName") + tActionName.Comment("An action (operation) name.") + tActionName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tActionName.Build()) + + tResourceName := rdl.NewStringTypeBuilder("ResourceName") + tResourceName.Comment("A shorthand for a YRN with no service or location. The 'tail' of a YRN, just the domain:entity. Note that the EntityName part is optional, that is, a domain name followed by a colon is valid resource name.") + tResourceName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*(:([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?") + sb.AddType(tResourceName.Build()) + + tYRN := rdl.NewStringTypeBuilder("YRN") + tYRN.Comment("A full Yahoo Resource name (YRN).") + tYRN.Pattern("(yrn:(([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?:(([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?:)?([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*(:([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?") + sb.AddType(tYRN.Build()) + + tYBase64 := rdl.NewStringTypeBuilder("YBase64") + tYBase64.Comment("The Y-specific URL-safe Base64 variant.") + tYBase64.Pattern("[a-zA-Z0-9\\._-]+") + sb.AddType(tYBase64.Build()) + + tYEncoded := rdl.NewStringTypeBuilder("YEncoded") + tYEncoded.Comment("YEncoded includes ybase64 chars, as well as = and %. This can represent a user cookie and URL-encoded values.") + tYEncoded.Pattern("[a-zA-Z0-9\\._%=-]*") + sb.AddType(tYEncoded.Build()) + + tAuthorityName := rdl.NewStringTypeBuilder("AuthorityName") + tAuthorityName.Comment("Used as the prefix in a signed assertion. This uniquely identifies a signing authority.") + tAuthorityName.Pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*") + sb.AddType(tAuthorityName.Build()) + + tSignedToken := rdl.NewStringTypeBuilder("SignedToken") + tSignedToken.Comment("A signed assertion if identity. i.e. the user cookie value. This token will only make sense to the authority that generated it, so it is beneficial to have something in the value that is cheaply recognized to quickly reject if it belongs to another authority. In addition to the YEncoded set our token includes ; to separate components and , to separate roles") + tSignedToken.Pattern("[a-zA-Z0-9\\._%=;,-]*") + sb.AddType(tSignedToken.Build()) + + tPublicKeyEntry := rdl.NewStructTypeBuilder("Struct", "PublicKeyEntry") + tPublicKeyEntry.Comment("The representation of the public key in a service identity object.") + tPublicKeyEntry.Field("key", "String", false, nil, "the public key for the service") + tPublicKeyEntry.Field("id", "String", false, nil, "the key identifier (version or zone name)") + sb.AddType(tPublicKeyEntry.Build()) + + tServiceIdentity := rdl.NewStructTypeBuilder("Struct", "ServiceIdentity") + tServiceIdentity.Comment("The representation of the service identity object.") + tServiceIdentity.Field("name", "ServiceName", false, nil, "the full name of the service, i.e. \"sports.storage\"") + tServiceIdentity.ArrayField("publicKeys", "PublicKeyEntry", true, "array of public keys for key rotation") + tServiceIdentity.Field("providerEndpoint", "String", true, nil, "if present, then this service can provision tenants via this endpoint.") + tServiceIdentity.Field("modified", "Timestamp", true, nil, "the timestamp when this entry was last modified") + tServiceIdentity.Field("executable", "String", true, nil, "the path of the executable that runs the service") + tServiceIdentity.ArrayField("hosts", "String", true, "list of host names that this service can run on") + tServiceIdentity.Field("user", "String", true, nil, "local (unix) user name this service can run as") + tServiceIdentity.Field("group", "String", true, nil, "local (unix) group name this service can run as") + sb.AddType(tServiceIdentity.Build()) + + tServiceIdentityList := rdl.NewStructTypeBuilder("Struct", "ServiceIdentityList") + tServiceIdentityList.Comment("The representation for an enumeration of services in the namespace.") + tServiceIdentityList.ArrayField("names", "EntityName", false, "list of service names") + sb.AddType(tServiceIdentityList.Build()) + + tHostServices := rdl.NewStructTypeBuilder("Struct", "HostServices") + tHostServices.Comment("The representation for an enumeration of services authorized to run on a specific host.") + tHostServices.Field("host", "String", false, nil, "name of the host") + tHostServices.ArrayField("names", "EntityName", false, "list of service names authorized to run on this host") + sb.AddType(tHostServices.Build()) + + tAssertionEffect := rdl.NewEnumTypeBuilder("Enum", "AssertionEffect") + tAssertionEffect.Comment("Every assertion can have the effect of ALLOW or DENY.") + tAssertionEffect.Element("ALLOW", "Every assertion can have the effect of ALLOW or DENY.") + tAssertionEffect.Element("DENY", "") + sb.AddType(tAssertionEffect.Build()) + + tAssertion := rdl.NewStructTypeBuilder("Struct", "Assertion") + tAssertion.Comment("A representation for the encapsulation of an action to be performed on a resource by a principal.") + tAssertion.Field("role", "String", false, nil, "the subject of the assertion, a role") + tAssertion.Field("resource", "String", false, nil, "the object of the assertion. Must be in the local namespace. Can contain wildcards") + tAssertion.Field("action", "String", false, nil, "the predicate of the assertion. Can contain wildcards") + tAssertion.Field("effect", "AssertionEffect", true, ALLOW, "the effect of the assertion in the policy language") + tAssertion.Field("id", "Int64", true, nil, "assertion id - auto generated by server") + sb.AddType(tAssertion.Build()) + + tPolicy := rdl.NewStructTypeBuilder("Struct", "Policy") + tPolicy.Comment("The representation for a Policy with set of assertions.") + tPolicy.Field("name", "ResourceName", false, nil, "name of the policy") + tPolicy.Field("modified", "Timestamp", true, nil, "last modification timestamp of this policy") + tPolicy.ArrayField("assertions", "Assertion", false, "list of defined assertions for this policy") + sb.AddType(tPolicy.Build()) + + tPolicyData := rdl.NewStructTypeBuilder("Struct", "PolicyData") + tPolicyData.Field("domain", "DomainName", false, nil, "name of the domain") + tPolicyData.ArrayField("policies", "Policy", false, "list of policies defined in this server") + sb.AddType(tPolicyData.Build()) + + tSignedPolicyData := rdl.NewStructTypeBuilder("Struct", "SignedPolicyData") + tSignedPolicyData.Comment("A representation of policies object defined in a given server.") + tSignedPolicyData.Field("policyData", "PolicyData", false, nil, "list of policies defined in a domain") + tSignedPolicyData.Field("zmsSignature", "String", false, nil, "zms signature generated based on the domain policies object") + tSignedPolicyData.Field("zmsKeyId", "String", false, nil, "the identifier of the zms key used to generate the signature") + tSignedPolicyData.Field("modified", "Timestamp", false, nil, "when the domain itself was last modified") + tSignedPolicyData.Field("expires", "Timestamp", false, nil, "timestamp specifying the expiration time for using this set of policies") + sb.AddType(tSignedPolicyData.Build()) + + tDomainSignedPolicyData := rdl.NewStructTypeBuilder("Struct", "DomainSignedPolicyData") + tDomainSignedPolicyData.Comment("A signed bulk transfer of policies. The data is signed with server's private key.") + tDomainSignedPolicyData.Field("signedPolicyData", "SignedPolicyData", false, nil, "policy data signed by ZMS") + tDomainSignedPolicyData.Field("signature", "String", false, nil, "signature generated based on the domain policies object") + tDomainSignedPolicyData.Field("keyId", "String", false, nil, "the identifier of the key used to generate the signature") + sb.AddType(tDomainSignedPolicyData.Build()) + + tRoleToken := rdl.NewStructTypeBuilder("Struct", "RoleToken") + tRoleToken.Comment("A representation of a signed RoleToken") + tRoleToken.Field("token", "String", false, nil, "") + tRoleToken.Field("expiryTime", "Int64", false, nil, "") + sb.AddType(tRoleToken.Build()) + + tAccess := rdl.NewStructTypeBuilder("Struct", "Access") + tAccess.Comment("Access can be checked and returned as this resource.") + tAccess.Field("granted", "Bool", false, nil, "true (allowed) or false (denied)") + sb.AddType(tAccess.Build()) + + tRoleAccess := rdl.NewStructTypeBuilder("Struct", "RoleAccess") + tRoleAccess.ArrayField("roles", "EntityName", false, "") + sb.AddType(tRoleAccess.Build()) + + tTenantDomains := rdl.NewStructTypeBuilder("Struct", "TenantDomains") + tTenantDomains.ArrayField("tenantDomainNames", "DomainName", false, "") + sb.AddType(tTenantDomains.Build()) + + tIdentity := rdl.NewStructTypeBuilder("Struct", "Identity") + tIdentity.Comment("Identity - a signed assertion of service or human identity, the response could be either a client certificate or just a regular NToken (depending if the request contained a csr or not).") + tIdentity.Field("name", "CompoundName", false, nil, "name of the identity, fully qualified, i.e. my.domain.service1, or aws.1232321321312.myusername") + tIdentity.Field("certificate", "String", true, nil, "a certificate usable for both client and server in TLS connections") + tIdentity.Field("caCertBundle", "String", true, nil, "the CA certificate chain to use with all IMS-generated certs") + tIdentity.Field("sshServerCert", "String", true, nil, "the SSH server cert, signed by the CA") + tIdentity.Field("serviceToken", "SignedToken", true, nil, "service token instead of TLS certificate") + tIdentity.MapField("attributes", "String", "String", true, "other config-like attributes determined at boot time") + sb.AddType(tIdentity.Build()) + + tInstanceInformation := rdl.NewStructTypeBuilder("Struct", "InstanceInformation") + tInstanceInformation.Comment("Instance object that includes requested service details plus host document that is signed by provider as part of the host bootstrap process") + tInstanceInformation.Field("document", "String", false, nil, "signed document containing attributes like IP address, instance-id, account#, etc.") + tInstanceInformation.Field("signature", "String", false, nil, "the signature for the document") + tInstanceInformation.Field("keyId", "String", false, nil, "the keyid used to sign the document") + tInstanceInformation.Field("domain", "CompoundName", false, nil, "the domain of the instance") + tInstanceInformation.Field("service", "SimpleName", false, nil, "the service this instance is supposed to run") + tInstanceInformation.Field("csr", "String", false, nil, "return a certificate in the response") + sb.AddType(tInstanceInformation.Build()) + + tInstanceRefreshRequest := rdl.NewStructTypeBuilder("Struct", "InstanceRefreshRequest") + tInstanceRefreshRequest.Comment("InstanceRefreshRequest - a certificate refresh request") + tInstanceRefreshRequest.Field("csr", "String", false, nil, "Cert CSR if requesting TLS certificate") + tInstanceRefreshRequest.Field("expiryTime", "Int32", true, nil, "in seconds how long token should be valid for") + sb.AddType(tInstanceRefreshRequest.Build()) + + tAWSInstanceInformation := rdl.NewStructTypeBuilder("Struct", "AWSInstanceInformation") + tAWSInstanceInformation.Comment("AWSInstanceInformation - the information a booting EC2 instance must provide to ZTS to authenticate.") + tAWSInstanceInformation.Field("document", "String", false, nil, "signed document containing attributes like IP address, instance-id, account#, etc.") + tAWSInstanceInformation.Field("signature", "String", false, nil, "the signature for the document") + tAWSInstanceInformation.Field("domain", "CompoundName", false, nil, "the domain of the instance") + tAWSInstanceInformation.Field("service", "SimpleName", false, nil, "the service this instance is supposed to run") + tAWSInstanceInformation.Field("csr", "String", false, nil, "return a certificate in the response") + tAWSInstanceInformation.Field("name", "CompoundName", false, nil, "the full service identity name (same as the EC2 instance profile name)") + tAWSInstanceInformation.Field("account", "SimpleName", false, nil, "the account id (as a string) for the instance. parsed from the instance profile ARN") + tAWSInstanceInformation.Field("cloud", "SimpleName", true, nil, "the name of the cloud (namespace) within the account, parsed from the name") + tAWSInstanceInformation.Field("subnet", "SimpleName", false, nil, "the name of the subnet this instance is expected to be running in, parsed from the name") + tAWSInstanceInformation.Field("access", "String", false, nil, "the AWS Access Key Id for the role") + tAWSInstanceInformation.Field("secret", "String", false, nil, "the AWS Secret Access Key for the role") + tAWSInstanceInformation.Field("token", "String", false, nil, "the AWS STS Token for the role") + tAWSInstanceInformation.Field("expires", "Timestamp", false, nil, "the expiration time of the access keys") + tAWSInstanceInformation.Field("modified", "Timestamp", false, nil, "the modified time of the access keys") + tAWSInstanceInformation.Field("flavor", "String", false, nil, "the 'flavor' of the access keys, i.e. \"AWS-HMAC\"") + sb.AddType(tAWSInstanceInformation.Build()) + + tAWSCertificateRequest := rdl.NewStructTypeBuilder("Struct", "AWSCertificateRequest") + tAWSCertificateRequest.Comment("AWSCertificateRequest - a certificate signing request") + tAWSCertificateRequest.Field("csr", "String", false, nil, "") + sb.AddType(tAWSCertificateRequest.Build()) + + tAWSTemporaryCredentials := rdl.NewStructTypeBuilder("Struct", "AWSTemporaryCredentials") + tAWSTemporaryCredentials.Field("accessKeyId", "String", false, nil, "") + tAWSTemporaryCredentials.Field("secretAccessKey", "String", false, nil, "") + tAWSTemporaryCredentials.Field("sessionToken", "String", false, nil, "") + tAWSTemporaryCredentials.Field("expiration", "Timestamp", false, nil, "") + sb.AddType(tAWSTemporaryCredentials.Build()) + + tDomainMetricType := rdl.NewEnumTypeBuilder("Enum", "DomainMetricType") + tDomainMetricType.Comment("zpe metric attributes") + tDomainMetricType.Element("ACCESS_ALLOWED", "zpe metric attributes") + tDomainMetricType.Element("ACCESS_ALLOWED_DENY", "") + tDomainMetricType.Element("ACCESS_ALLOWED_DENY_NO_MATCH", "") + tDomainMetricType.Element("ACCESS_ALLOWED_ALLOW", "") + tDomainMetricType.Element("ACCESS_ALLOWED_ERROR", "") + tDomainMetricType.Element("ACCESS_ALLOWED_TOKEN_INVALID", "") + tDomainMetricType.Element("ACCESS_Allowed_TOKEN_EXPIRED", "") + tDomainMetricType.Element("ACCESS_ALLOWED_DOMAIN_NOT_FOUND", "") + tDomainMetricType.Element("ACCESS_ALLOWED_DOMAIN_MISMATCH", "") + tDomainMetricType.Element("ACCESS_ALLOWED_DOMAIN_EXPIRED", "") + tDomainMetricType.Element("ACCESS_ALLOWED_DOMAIN_EMPTY", "") + tDomainMetricType.Element("ACCESS_ALLOWED_TOKEN_CACHE_FAILURE", "") + tDomainMetricType.Element("ACCESS_ALLOWED_TOKEN_CACHE_NOT_FOUND", "") + tDomainMetricType.Element("ACCESS_ALLOWED_TOKEN_CACHE_SUCCESS", "") + tDomainMetricType.Element("ACCESS_ALLOWED_TOKEN_VALIDATE", "") + tDomainMetricType.Element("LOAD_FILE_FAIL", "") + tDomainMetricType.Element("LOAD_FILE_GOOD", "") + tDomainMetricType.Element("LOAD_DOMAIN_GOOD", "") + sb.AddType(tDomainMetricType.Build()) + + tDomainMetric := rdl.NewStructTypeBuilder("Struct", "DomainMetric") + tDomainMetric.Field("metricType", "DomainMetricType", false, nil, "") + tDomainMetric.Field("metricVal", "Int32", false, nil, "") + sb.AddType(tDomainMetric.Build()) + + tDomainMetrics := rdl.NewStructTypeBuilder("Struct", "DomainMetrics") + tDomainMetrics.Field("domainName", "DomainName", false, nil, "name of the domain the metrics pertain to") + tDomainMetrics.ArrayField("metricList", "DomainMetric", false, "list of the domains metrics") + sb.AddType(tDomainMetrics.Build()) + + rGetServiceIdentity := rdl.NewResourceBuilder("ServiceIdentity", "GET", "/domain/{domainName}/service/{serviceName}") + rGetServiceIdentity.Comment("Get info for the specified ServiceIdentity.") + rGetServiceIdentity.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetServiceIdentity.Input("serviceName", "ServiceName", true, "", "", false, nil, "name of the service to be retrieved") + rGetServiceIdentity.Auth("", "", true, "") + rGetServiceIdentity.Exception("BAD_REQUEST", "ResourceError", "") + rGetServiceIdentity.Exception("NOT_FOUND", "ResourceError", "") + rGetServiceIdentity.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetServiceIdentity.Build()) + + rGetServiceIdentityList := rdl.NewResourceBuilder("ServiceIdentityList", "GET", "/domain/{domainName}/service") + rGetServiceIdentityList.Comment("Enumerate services provisioned in this domain.") + rGetServiceIdentityList.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetServiceIdentityList.Auth("", "", true, "") + rGetServiceIdentityList.Exception("BAD_REQUEST", "ResourceError", "") + rGetServiceIdentityList.Exception("NOT_FOUND", "ResourceError", "") + rGetServiceIdentityList.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetServiceIdentityList.Build()) + + rGetPublicKeyEntry := rdl.NewResourceBuilder("PublicKeyEntry", "GET", "/domain/{domainName}/service/{serviceName}/publickey/{keyId}") + rGetPublicKeyEntry.Comment("Retrieve the specified public key from the service.") + rGetPublicKeyEntry.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetPublicKeyEntry.Input("serviceName", "SimpleName", true, "", "", false, nil, "name of the service") + rGetPublicKeyEntry.Input("keyId", "String", true, "", "", false, nil, "the identifier of the public key to be retrieved") + rGetPublicKeyEntry.Exception("BAD_REQUEST", "ResourceError", "") + rGetPublicKeyEntry.Exception("NOT_FOUND", "ResourceError", "") + sb.AddResource(rGetPublicKeyEntry.Build()) + + rGetHostServices := rdl.NewResourceBuilder("HostServices", "GET", "/host/{host}/services") + rGetHostServices.Comment("Enumerate services provisioned on a specific host") + rGetHostServices.Input("host", "String", true, "", "", false, nil, "name of the host") + rGetHostServices.Exception("BAD_REQUEST", "ResourceError", "") + sb.AddResource(rGetHostServices.Build()) + + rGetDomainSignedPolicyData := rdl.NewResourceBuilder("DomainSignedPolicyData", "GET", "/domain/{domainName}/signed_policy_data") + rGetDomainSignedPolicyData.Comment("Get a signed policy enumeration from the service, to transfer to a local store. An ETag is generated for the PolicyList that changes when any item in the list changes. If the If-None-Match header is provided, and it matches the ETag that would be returned, then a NOT_MODIFIED response is returned instead of the list.") + rGetDomainSignedPolicyData.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetDomainSignedPolicyData.Input("matchingTag", "String", false, "", "If-None-Match", false, nil, "Retrieved from the previous request, this timestamp specifies to the server to return any policies modified since this time") + rGetDomainSignedPolicyData.Output("tag", "String", "ETag", false, "") + rGetDomainSignedPolicyData.Exception("BAD_REQUEST", "ResourceError", "") + rGetDomainSignedPolicyData.Exception("NOT_FOUND", "ResourceError", "") + sb.AddResource(rGetDomainSignedPolicyData.Build()) + + rGetRoleToken := rdl.NewResourceBuilder("RoleToken", "GET", "/domain/{domainName}/token") + rGetRoleToken.Comment("Return a security token for the specific role in the namespace that the user can assume. If the role is omitted, then all roles in the namespace that the authenticated user can assume are returned. the caller can specify how long the RoleToken should be valid for by specifying the minExpiryTime and maxExpiryTime parameters. The minExpiryTime specifies that the returned RoleToken must be at least valid (min/lower bound) for specified number of seconds, while maxExpiryTime specifies that the RoleToken must be at most valid (max/upper bound) for specified number of seconds. If both values are the same, the server must return a RoleToken for that many seconds. If no values are specified, the server's default RoleToken Timeout value is used.") + rGetRoleToken.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetRoleToken.Input("role", "EntityName", false, "role", "", true, nil, "only interested for a token for this role") + rGetRoleToken.Input("minExpiryTime", "Int32", false, "minExpiryTime", "", true, nil, "in seconds min expiry time") + rGetRoleToken.Input("maxExpiryTime", "Int32", false, "maxExpiryTime", "", true, nil, "in seconds max expiry time") + rGetRoleToken.Input("proxyForPrincipal", "EntityName", false, "proxyForPrincipal", "", true, nil, "optional this request is proxy for this principal") + rGetRoleToken.Auth("", "", true, "") + rGetRoleToken.Exception("BAD_REQUEST", "ResourceError", "") + rGetRoleToken.Exception("FORBIDDEN", "ResourceError", "") + rGetRoleToken.Exception("NOT_FOUND", "ResourceError", "") + rGetRoleToken.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetRoleToken.Build()) + + rGetAccess := rdl.NewResourceBuilder("Access", "GET", "/access/domain/{domainName}/role/{roleName}/principal/{principal}") + rGetAccess.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetAccess.Input("roleName", "EntityName", true, "", "", false, nil, "name of the role to check access for") + rGetAccess.Input("principal", "EntityName", true, "", "", false, nil, "carry out the access check for this principal") + rGetAccess.Auth("", "", true, "") + rGetAccess.Exception("BAD_REQUEST", "ResourceError", "") + rGetAccess.Exception("FORBIDDEN", "ResourceError", "") + rGetAccess.Exception("NOT_FOUND", "ResourceError", "") + rGetAccess.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetAccess.Build()) + + rGetRoleAccess := rdl.NewResourceBuilder("RoleAccess", "GET", "/access/domain/{domainName}/principal/{principal}") + rGetRoleAccess.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain") + rGetRoleAccess.Input("principal", "EntityName", true, "", "", false, nil, "carry out the role access lookup for this principal") + rGetRoleAccess.Auth("", "", true, "") + rGetRoleAccess.Exception("BAD_REQUEST", "ResourceError", "") + rGetRoleAccess.Exception("NOT_FOUND", "ResourceError", "") + rGetRoleAccess.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetRoleAccess.Build()) + + rGetTenantDomains := rdl.NewResourceBuilder("TenantDomains", "GET", "/providerdomain/{providerDomainName}/user/{userName}") + rGetTenantDomains.Comment("Get list of tenant domains user has access to for specified provider domain and service") + rGetTenantDomains.Input("providerDomainName", "DomainName", true, "", "", false, nil, "name of the provider domain") + rGetTenantDomains.Input("userName", "EntityName", true, "", "", false, nil, "name of the user to retrieve tenant domain access for") + rGetTenantDomains.Input("roleName", "EntityName", false, "roleName", "", true, nil, "role name to filter on when looking for the tenants in provider") + rGetTenantDomains.Input("serviceName", "ServiceName", false, "serviceName", "", true, nil, "service name to filter on when looking for the tenants in provider") + rGetTenantDomains.Auth("", "", true, "") + rGetTenantDomains.Exception("BAD_REQUEST", "ResourceError", "") + rGetTenantDomains.Exception("NOT_FOUND", "ResourceError", "") + rGetTenantDomains.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetTenantDomains.Build()) + + rPostInstanceInformation := rdl.NewResourceBuilder("Identity", "POST", "/instance") + rPostInstanceInformation.Comment("Get a cert for service being bootstrapped by supported service") + rPostInstanceInformation.Input("info", "InstanceInformation", false, "", "", false, nil, "") + rPostInstanceInformation.Exception("BAD_REQUEST", "ResourceError", "") + rPostInstanceInformation.Exception("FORBIDDEN", "ResourceError", "") + rPostInstanceInformation.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPostInstanceInformation.Build()) + + rPostInstanceRefreshRequest := rdl.NewResourceBuilder("Identity", "POST", "/instance/{domain}/{service}/refresh") + rPostInstanceRefreshRequest.Comment("Refresh self identity if the original identity was issued by ZTS") + rPostInstanceRefreshRequest.Input("domain", "CompoundName", true, "", "", false, nil, "name of the domain requesting the refresh") + rPostInstanceRefreshRequest.Input("service", "SimpleName", true, "", "", false, nil, "name of the service requesting the refresh") + rPostInstanceRefreshRequest.Input("req", "InstanceRefreshRequest", false, "", "", false, nil, "the refresh request") + rPostInstanceRefreshRequest.Auth("", "", true, "") + rPostInstanceRefreshRequest.Exception("BAD_REQUEST", "ResourceError", "") + rPostInstanceRefreshRequest.Exception("FORBIDDEN", "ResourceError", "") + rPostInstanceRefreshRequest.Exception("NOT_FOUND", "ResourceError", "") + rPostInstanceRefreshRequest.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPostInstanceRefreshRequest.Build()) + + rPostAWSInstanceInformation := rdl.NewResourceBuilder("Identity", "POST", "/aws/instance") + rPostAWSInstanceInformation.Comment("Register an instance in AWS ZTS. Whether this succeeds or not depends on the contents of the request (the request itself is not authenticated or authorized in the normal way). If successful, the Identity is returned as a x.509 client certificate (to be used in TLS operations)") + rPostAWSInstanceInformation.Input("info", "AWSInstanceInformation", false, "", "", false, nil, "") + rPostAWSInstanceInformation.Exception("BAD_REQUEST", "ResourceError", "") + rPostAWSInstanceInformation.Exception("FORBIDDEN", "ResourceError", "") + rPostAWSInstanceInformation.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPostAWSInstanceInformation.Build()) + + rPostAWSCertificateRequest := rdl.NewResourceBuilder("Identity", "POST", "/aws/instance/{domain}/{service}/refresh") + rPostAWSCertificateRequest.Comment("Rotate certs. Make this request with previous cert, the result is new cert for the same identity.") + rPostAWSCertificateRequest.Input("domain", "CompoundName", true, "", "", false, nil, "name of the domain requesting the refresh") + rPostAWSCertificateRequest.Input("service", "SimpleName", true, "", "", false, nil, "name of the service requesting the refresh") + rPostAWSCertificateRequest.Input("req", "AWSCertificateRequest", false, "", "", false, nil, "the refresh request") + rPostAWSCertificateRequest.Auth("", "", true, "") + rPostAWSCertificateRequest.Exception("BAD_REQUEST", "ResourceError", "") + rPostAWSCertificateRequest.Exception("FORBIDDEN", "ResourceError", "") + rPostAWSCertificateRequest.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPostAWSCertificateRequest.Build()) + + rGetAWSTemporaryCredentials := rdl.NewResourceBuilder("AWSTemporaryCredentials", "GET", "/domain/{domainName}/role/{role}/creds") + rGetAWSTemporaryCredentials.Comment("perform an AWS AssumeRole of the target role and return the credentials. ZTS must have been granted the ability to assume the role in IAM, and granted the ability to ASSUME_AWS_ROLE in Athenz for this to succeed.") + rGetAWSTemporaryCredentials.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain containing the role, which implies the target account") + rGetAWSTemporaryCredentials.Input("role", "CompoundName", true, "", "", false, nil, "the target AWS role name in the domain account, in Athenz terms, i.e. \"the.role\"") + rGetAWSTemporaryCredentials.Auth("", "", true, "") + rGetAWSTemporaryCredentials.Exception("BAD_REQUEST", "ResourceError", "") + rGetAWSTemporaryCredentials.Exception("FORBIDDEN", "ResourceError", "") + rGetAWSTemporaryCredentials.Exception("NOT_FOUND", "ResourceError", "") + rGetAWSTemporaryCredentials.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rGetAWSTemporaryCredentials.Build()) + + rPostDomainMetrics := rdl.NewResourceBuilder("DomainMetrics", "POST", "/metrics/{domainName}") + rPostDomainMetrics.Comment("called to post multiple zpe related metric attributes") + rPostDomainMetrics.Input("domainName", "DomainName", true, "", "", false, nil, "name of the domain the metrics pertain to") + rPostDomainMetrics.Input("req", "DomainMetrics", false, "", "", false, nil, "") + rPostDomainMetrics.Exception("BAD_REQUEST", "ResourceError", "") + rPostDomainMetrics.Exception("FORBIDDEN", "ResourceError", "") + rPostDomainMetrics.Exception("NOT_FOUND", "ResourceError", "") + rPostDomainMetrics.Exception("UNAUTHORIZED", "ResourceError", "") + sb.AddResource(rPostDomainMetrics.Build()) + + schema = sb.Build() +} + +func ZTSSchema() *rdl.Schema { + return schema +} diff --git a/clients/java/sia/README.md b/clients/java/sia/README.md new file mode 100644 index 00000000000..89bb17098d2 --- /dev/null +++ b/clients/java/sia/README.md @@ -0,0 +1,27 @@ +Service Identity Agent Client Java Library + +System properties: + + athenz.sia.client.ntoken_path + When used in athenz enabled environment, this setting specifies + the path of the file that contains the ntoken generated by the framework. + This setting requires that ntoken_domain and ntoken_service settings + to be configured as well. + + athenz.sia.client.ntoken_domain + This setting specifies the domain name of the service that is identified + by the ntoken generated by the framework(see ntoken_path above). + This setting requires that ntoken_path and ntoken_service settings + to be configured as well. + + athenz.sia.client.ntoken_service + This setting specifies the service name that is identified + by the ntoken generated by the framework(see ntoken_path above). + This setting requires that ntoken_path and ntoken_domain settings + to be configured as well. + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/clients/java/sia/pom.xml b/clients/java/sia/pom.xml new file mode 100644 index 00000000000..c379b49c3a9 --- /dev/null +++ b/clients/java/sia/pom.xml @@ -0,0 +1,62 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../../pom.xml + + + sia_java_client + jar + sia_java_client + SIA Java Client Library + + + + ${project.groupId} + auth_core + ${project.parent.version} + + + com.kohlschutter.junixsocket + junixsocket-common + 2.0.4 + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + prepare-package + + jar + + + + + + + + + diff --git a/clients/java/sia/src/main/java/com/yahoo/athenz/sia/SIA.java b/clients/java/sia/src/main/java/com/yahoo/athenz/sia/SIA.java new file mode 100644 index 00000000000..d6279277d26 --- /dev/null +++ b/clients/java/sia/src/main/java/com/yahoo/athenz/sia/SIA.java @@ -0,0 +1,47 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.sia; + +import java.io.IOException; +import java.util.ArrayList; + +import com.yahoo.athenz.auth.Principal; + +public interface SIA { + + /** + * For the specified domain/service return the corresponding Service Principal that + * includes the SIA generated PrincipalToken (NToken) + * @param domainName name of the domain + * @param serviceName name of the service + * @param minExpiryTime (optional) specifies that the returned PrincipalToken must be + * at least valid (min/lower bound) for specified number of seconds, + * @param maxExpiryTime (optional) specifies that the returned PrincipalToken must be + * at most valid (max/upper bound) for specified number of seconds. + * @param ignoreCache ignore the cache and retrieve the token from SIA Server + * @return SIA generated Principal object with PrincipalToken + * @throws IOException for any IO errors + */ + public Principal getServicePrincipal(String domainName, String serviceName, + Integer minExpiryTime, Integer maxExpiryTime, boolean ignoreCache) throws IOException; + + /** + * Returns the list of domains that have private keys registered on this host + * @return List of domain names + * @throws IOException for any IO errors + */ + public ArrayList getDomainList() throws IOException; +} diff --git a/clients/java/sia/src/main/java/com/yahoo/athenz/sia/impl/SIAClient.java b/clients/java/sia/src/main/java/com/yahoo/athenz/sia/impl/SIAClient.java new file mode 100644 index 00000000000..598bd5f646c --- /dev/null +++ b/clients/java/sia/src/main/java/com/yahoo/athenz/sia/impl/SIAClient.java @@ -0,0 +1,526 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.sia.impl; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; + +import org.newsclub.net.unix.AFUNIXSocket; +import org.newsclub.net.unix.AFUNIXSocketAddress; +import org.newsclub.net.unix.AFUNIXSocketException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.PrincipalAuthority; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.token.PrincipalToken; +import com.yahoo.athenz.sia.SIA; +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Struct; + +/** + * SIA Client Library to retrieve Service Principal and the list of ZMS Domains + * provisioned to run on this host. + */ +public class SIAClient implements SIA { + + private static final Logger LOG = LoggerFactory.getLogger(SIAClient.class); + + public static final int OP_GET_NTOKEN = 1; + public static final int OP_LIST_DOMAINS = 2; + + private static int tokenMinExpiryTime = 1800; + private static final String UNIX_SOCKET_FNAME = "/var/run/sia/sia.ds"; + private static final String ROOT_DIR = "/home/athenz"; + private static final Authority PRINCIPAL_AUTHORITY = new PrincipalAuthority(); + + // Used for testing: if set, should point to the config file containing vars specifying + // the ntoken is in a file. + static final String SIA_PROP_CFG_FILE = "athenz.sia.client.config_path"; + + // vars used to set the config file + static final String SIACLT_NTOKEN_PATH = "ntoken_path"; + static final String SIACLT_NTOKEN_DOMAIN = "ntoken_domain"; + static final String SIACLT_NTOKEN_SERVICE = "ntoken_service"; + + // system property vars + static final String SIA_PROP_NTOKEN_PATH = "athenz.sia.client.ntoken_path"; + static final String SIA_PROP_NTOKEN_DOMAIN = "athenz.sia.client.ntoken_domain"; + static final String SIA_PROP_NTOKEN_SERVICE = "athenz.sia.client.ntoken_service"; + + static ConcurrentHashMap principalTokenCache = new ConcurrentHashMap<>(); + + // values obtained from the system properties and config file + // + static String cfgNtokenPath = null; + static String cfgNtokenDomain = null; + static String cfgNtokenService = null; + + final static String CONF_PATH = "/conf/sia_java_client/sia_client.conf"; + + static { + initConfigVars(); + } + + static void initConfigVars() { + String ntokenPath = null; + String ntokenDomain = null; + String ntokenSvc = null; + + // load the config vars. in case of any exceptions + // we'll just set our config variables to null and default to using + // SIA Server for principal tokens + + Struct configVars = null; + try { + String confFile = System.getProperty(SIA_PROP_CFG_FILE); + if (confFile == null) { + String rootDir = System.getenv("ROOT"); + if (null == rootDir) { + rootDir = File.separator + "home" + File.separator + "athenz"; + } + confFile = rootDir + CONF_PATH; + } + + Path path = Paths.get(confFile); + configVars = JSON.fromBytes(Files.readAllBytes(path), Struct.class); + + setupConfigVars(configVars.getString(SIACLT_NTOKEN_PATH), + configVars.getString(SIACLT_NTOKEN_DOMAIN), + configVars.getString(SIACLT_NTOKEN_SERVICE)); + } catch (Exception exc) { + LOG.error("SIACLT: config variable initialization failure. Will use SIA Server for Principal Tokens", exc); + cfgNtokenPath = null; + cfgNtokenDomain = null; + cfgNtokenService = null; + return; + } + + // load the System properties + // if set, they over-ride the config variables. in case of any exceptions + // we'll just set our config variables to null and default to using + // SIA Server for principal tokens + + try { + ntokenPath = System.getProperty(SIA_PROP_NTOKEN_PATH); + ntokenDomain = System.getProperty(SIA_PROP_NTOKEN_DOMAIN); + ntokenSvc = System.getProperty(SIA_PROP_NTOKEN_SERVICE); + } catch (Exception exc) { + LOG.error("SIACLT: system property initialization failure. Will use SIA Server for Principal Tokens", exc); + cfgNtokenPath = null; + cfgNtokenDomain = null; + cfgNtokenService = null; + return; + } + + if (ntokenPath == null || ntokenPath.isEmpty()) { + ntokenPath = cfgNtokenPath; + } + if (ntokenDomain == null || ntokenDomain.isEmpty()) { + ntokenDomain = cfgNtokenDomain; + } + if (ntokenSvc == null || ntokenSvc.isEmpty()) { + ntokenSvc = cfgNtokenService; + } + setupConfigVars(ntokenPath, ntokenDomain, ntokenSvc); + } + + static void setupConfigVars(String ntokenPath, String ntokenDomain, String ntokenSvc) throws IllegalArgumentException { + + if (LOG.isDebugEnabled()) { + LOG.debug("SIACLT:setupConfigVars: ntoken path=" + ntokenPath + + " domain=" + ntokenDomain + " service=" + ntokenSvc); + } + + // make sure no empty strings(ie. "") were specified + // + if (ntokenPath != null && ntokenPath.isEmpty()) { + cfgNtokenPath = null; + } else { + cfgNtokenPath = ntokenPath; + } + + if (ntokenDomain != null) { + if (ntokenDomain.isEmpty()) { + cfgNtokenDomain = null; + } else { + cfgNtokenDomain = ntokenDomain.toLowerCase(); + } + } + + if (ntokenSvc != null) { + if (ntokenSvc.isEmpty()) { + cfgNtokenService = null; + } else { + cfgNtokenService = ntokenSvc.toLowerCase(); + } + } + + if (!((cfgNtokenPath == null && cfgNtokenDomain == null && cfgNtokenService == null) || + (cfgNtokenPath != null && cfgNtokenDomain != null && cfgNtokenService != null))) { + String errMsg = "SIACLT: invalid ntoken configuration settings: " + + "ntoken_path, ntoken_domain, ntoken_service must all be set " + + "to use the client in a managed Athenz enabled environment"; + LOG.error(errMsg); + throw new IllegalArgumentException(errMsg); + } + + // build the service only if we have configured valid values + + if (cfgNtokenDomain != null) { + StringBuilder sb = new StringBuilder(512); + sb.append(cfgNtokenDomain).append(".").append(cfgNtokenService); + cfgNtokenService = sb.toString(); + } + } + + public SIAClient() { + } + + String siaSocketFile() { + + String root = System.getenv("ROOT"); + if (root == null) { + root = ROOT_DIR; + } + + return root + UNIX_SOCKET_FNAME; + } + + String getPrincipalTokenCacheKey(String domainName, String serviceName) { + + StringBuilder cacheKey = new StringBuilder(512); + cacheKey.append(domainName); + cacheKey.append("."); + cacheKey.append(serviceName); + return cacheKey.toString(); + } + + boolean isExpiredToken(long expiryTime, Integer minExpiryTime, Integer maxExpiryTime) { + + // we'll first make sure if we're given both min and max expiry + // times then both conditions are satisfied + + if (minExpiryTime != null && expiryTime < minExpiryTime) { + return true; + } + + if (maxExpiryTime != null && expiryTime > maxExpiryTime) { + return true; + } + + // if both limits were null then we need to make sure + // that our token is valid for our min configured value + + if (minExpiryTime == null && maxExpiryTime == null && expiryTime < tokenMinExpiryTime) { + return true; + } + + return false; + } + + PrincipalToken lookupPrincipalTokenInCache(String cacheKey, Integer minExpiryTime, Integer maxExpiryTime) { + + PrincipalToken principalToken = principalTokenCache.get(cacheKey); + if (principalToken == null) { + return null; + } + + // before returning our cache hit we need to make sure it + // satisfies the time requirements as specified by the client + + long expiryTime = principalToken.getExpiryTime() - (System.currentTimeMillis() / 1000); + + if (isExpiredToken(expiryTime, minExpiryTime, maxExpiryTime)) { + principalTokenCache.remove(cacheKey); + return null; + } + + return principalToken; + } + + int readResponseData(InputStream is, byte[] data) throws IOException { + + int read; + int offset = 0; + int length = data.length; + + while (true) { + read = is.read(data, offset, length); + if (read == -1) { + break; + } + + offset += read; + length -= read; + + if (length == 0) { + break; + } + } + + return offset; + } + + Socket getSIADomainSocket() throws IOException { + File socketFile = new File(siaSocketFile()); + AFUNIXSocket sock = AFUNIXSocket.newInstance(); + try { + sock.connect(new AFUNIXSocketAddress(socketFile)); + } catch (AFUNIXSocketException e) { + throw e; + } + return sock; + } + + String processRequest(Socket sock, int sia_op, byte[] data) throws IOException { + + String response = null; + try (InputStream is = sock.getInputStream(); + OutputStream os = sock.getOutputStream()) { + + // first we are going to write our magic number + + ByteBuffer dsBytes = ByteBuffer.allocate(4); + dsBytes.order(ByteOrder.LITTLE_ENDIAN); + dsBytes.putInt(0x534941); + os.write(dsBytes.array()); + + // next we are going to write our operation code + + dsBytes.clear(); + dsBytes.putInt(sia_op); + os.write(dsBytes.array()); + + // next write the length of our data which should be 4 bytes + + dsBytes.clear(); + dsBytes.putInt(data != null ? data.length : 0); + os.write(dsBytes.array()); + + // now write our data + + if (data != null) { + os.write(data); + } + + os.flush(); + + // first read the response status + + byte[] retCodeBytes = new byte[4]; + int read = readResponseData(is, retCodeBytes); + if (read != 4) { + throw new IOException("Unable to read response return code"); + } + + ByteBuffer retBytes = ByteBuffer.wrap(retCodeBytes); + retBytes.order(ByteOrder.LITTLE_ENDIAN); + int retCode = retBytes.getInt(); + + if (retCode != 0) { + throw new IOException("Server failed to process request - error: " + retCode); + } + + // next read the response length + + byte[] dataSize = new byte[4]; + read = readResponseData(is, dataSize); + if (read != 4) { + throw new IOException("Unable to read response length"); + } + + ByteBuffer dataBytes = ByteBuffer.wrap(dataSize); + dataBytes.order(ByteOrder.LITTLE_ENDIAN); + int dataLen = dataBytes.getInt(); + + // now read the rest of the data + + byte[] buf = new byte[dataLen]; + read = readResponseData(is, buf); + if (read != dataLen) { + throw new IOException("Read partial data: " + read + " vs. " + dataLen); + } + + response = new String(buf, "UTF-8"); + } + + return response; + } + + byte[] tokenRequestBuilder(String domainName, String serviceName, Integer maxExpiryTime) { + + StringBuilder reqBuilder = new StringBuilder(512); + reqBuilder.append("d="); + reqBuilder.append(domainName); + reqBuilder.append(",s="); + reqBuilder.append(serviceName); + if (maxExpiryTime != null) { + reqBuilder.append(",e="); + reqBuilder.append(maxExpiryTime); + } + return reqBuilder.toString().getBytes(StandardCharsets.UTF_8); + } + + String getSIAPrincipalToken(String domainName, String serviceName, Integer maxExpiryTime) throws IOException { + + byte[] req = tokenRequestBuilder(domainName, serviceName, maxExpiryTime); + + String token = null; + Socket sock = null; + try { + sock = getSIADomainSocket(); + token = processRequest(sock, OP_GET_NTOKEN, req); + } finally { + if (sock != null) { + sock.close(); + } + } + + return token; + } + + /** + * For the specified domain/service return the corresponding Service Principal that + * includes the SIA generated PrincipalToken (NToken) + * @param domainName name of the domain + * @param serviceName name of the service + * @param minExpiryTime (optional) specifies that the returned PrincipalToken must be + * at least valid (min/lower bound) for specified number of seconds, + * @param maxExpiryTime (optional) specifies that the returned PrincipalToken must be + * at most valid (max/upper bound) for specified number of seconds. + * @param ignoreCache ignore the cache and retrieve the token from SIA Server + * @return SIA generated Principal object with PrincipalToken + * @throws IOException for IO errors + */ + public Principal getServicePrincipal(String domainName, String serviceName, + Integer minExpiryTime, Integer maxExpiryTime, boolean ignoreCache) + throws IOException { + + if (domainName == null || domainName.isEmpty() || + serviceName == null || serviceName.isEmpty()) { + + String errMsg = "get service principal: both domain and service names are required"; + LOG.error("SIACLT: " + errMsg); + throw new IOException(errMsg); + } + + // normalize our domain and service names to lower case + + domainName = domainName.toLowerCase(); + serviceName = serviceName.toLowerCase(); + + // first lookup in our cache to see if it can be satisfied + // only if we're not asked to ignore the cache + + String nToken = null; + String cacheKey = getPrincipalTokenCacheKey(domainName, serviceName); + if (!ignoreCache) { + PrincipalToken principalToken = lookupPrincipalTokenInCache(cacheKey, minExpiryTime, maxExpiryTime); + if (principalToken != null) { + nToken = principalToken.getSignedToken(); + } + } + + if (nToken == null) { + + // get ntoken from file path if configured, else retrieve from SIA server + // + if (cfgNtokenPath != null) { + nToken = getFilePrincipalToken(domainName, serviceName); + } else { + + // retrieve a new PrincipalToken from SIA Server if we didn't + // satisfy the request from our cache + // + nToken = getSIAPrincipalToken(domainName, serviceName, maxExpiryTime); + + // create and put a new PrincipalToken object in the cache and + // return a newly created principal object + + PrincipalToken principalToken = new PrincipalToken(nToken); + principalTokenCache.put(cacheKey, principalToken); + } + } + + return SimplePrincipal.create(domainName, serviceName, nToken, PRINCIPAL_AUTHORITY); + } + + String getFilePrincipalToken(String domainName, String serviceName) throws IOException { + StringBuilder sb = new StringBuilder(512); + sb.append(domainName).append("."). append(serviceName); + String svc = sb.toString().toLowerCase(); + if (!svc.equals(cfgNtokenService)) { + String errMsg = "SIACLT: get ntoken from file: Unknown service=" + + svc + " Configured service=" + cfgNtokenService; + LOG.error(errMsg); + throw new IOException(errMsg); + } + + Path path = Paths.get(cfgNtokenPath); + String token = new String(Files.readAllBytes(path)); + if (token != null && !token.isEmpty()) { + token = token.trim(); + int index = token.indexOf('\n'); + if (index != -1) { + token = token.substring(0, index); + } + } + return token; + } + + /** + * Returns the list of domains that have private keys registered on this host + * @return List of domain names + * @throws IOException for any IO errors + */ + public ArrayList getDomainList() throws IOException { + + String domains = null; + Socket sock = null; + try { + sock = getSIADomainSocket(); + domains = processRequest(sock, OP_LIST_DOMAINS, null); + } finally { + if (sock != null) { + sock.close(); + } + } + + StringTokenizer st = new StringTokenizer(domains, ";"); + ArrayList domainList = new ArrayList(); + while (st.hasMoreTokens()) { + domainList.add(st.nextToken()); + } + + return domainList; + } +} diff --git a/clients/java/sia/src/test/java/com/yahoo/athenz/sia/impl/SIAClientTest.java b/clients/java/sia/src/test/java/com/yahoo/athenz/sia/impl/SIAClientTest.java new file mode 100644 index 00000000000..8eebf8c2caf --- /dev/null +++ b/clients/java/sia/src/test/java/com/yahoo/athenz/sia/impl/SIAClientTest.java @@ -0,0 +1,528 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.sia.impl; + +import java.io.File; +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.FileOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.token.PrincipalToken; +import com.yahoo.athenz.sia.impl.SIAClient; + +public class SIAClientTest { + + @Mock Socket mockSocket; + + @BeforeClass + public void setUp() throws Exception { + System.setProperty(SIAClient.SIA_PROP_NTOKEN_PATH, "src/test/resources/svc.ntoken"); + System.setProperty(SIAClient.SIA_PROP_NTOKEN_SERVICE, "storage"); + System.setProperty(SIAClient.SIA_PROP_CFG_FILE, "src/test/resources/testcfg.conf"); + + MockitoAnnotations.initMocks(this); + prepareNtokenFile("_test"); + } + + // writes an ntoken to the configured ntoken path + void prepareNtokenFile(String pathSuffix) throws Exception { + + // prepare the ntoken file + // ex: ntoken = "v=S1;d=athenz;n=storage;t=55555;e=57955;s=fake"; + String ntoken_path = System.getProperty(SIAClient.SIA_PROP_NTOKEN_PATH); + ntoken_path = ntoken_path + pathSuffix; + Path path = Paths.get(ntoken_path); + String token = new String(Files.readAllBytes(path)); + long curTimeSecs = System.currentTimeMillis() / 1000; + String curTime = Long.toString(curTimeSecs); + token = token.replaceFirst("55555", curTime); + curTimeSecs += 2400; + curTime = Long.toString(curTimeSecs); + token = token.replaceFirst("55555", curTime); + + // write it to the configured ntoken file + ntoken_path = System.getProperty(SIAClient.SIA_PROP_NTOKEN_PATH); + File file = new File(ntoken_path); + file.createNewFile(); + Writer fw = new OutputStreamWriter(new FileOutputStream(file)); + fw.write(token + "\n"); + fw.close(); + } + + @AfterMethod + public void cleanup() { + } + + @Test + public void testIsExpiredTokenSmallerThanMin() { + SIAClient client = new SIAClient(); + assertTrue(client.isExpiredToken(100, 200, null)); + } + + @Test + public void testIsExpiredTokenBiggerThanMax() { + SIAClient client = new SIAClient(); + assertTrue(client.isExpiredToken(500, null, 300)); + assertTrue(client.isExpiredToken(500, 200, 300)); + } + + @Test + public void testIsExpiredTokenAtLeastOneLimitIsNotNull() { + SIAClient client = new SIAClient(); + assertFalse(client.isExpiredToken(500, null, 600)); + assertFalse(client.isExpiredToken(500, 200, null)); + assertFalse(client.isExpiredToken(500, 200, 501)); + } + + @Test + public void testIsExpiredTokenAtLeastBothLimitsNullSmallerThanMin() { + // the min is 1800 + SIAClient client = new SIAClient(); + assertTrue(client.isExpiredToken(1700, null, null)); + } + + @Test + public void testIsExpiredTokenAtLeastBothLimitsNullBiggerThanMin() { + // the min is 1800 + SIAClient client = new SIAClient(); + assertFalse(client.isExpiredToken(2100, null, null)); + } + + @Test + public void testGetPrincipalTokenCacheKey() { + SIAClient client = new SIAClient(); + assertEquals(client.getPrincipalTokenCacheKey("coretech", "service"), "coretech.service"); + assertEquals(client.getPrincipalTokenCacheKey(null, "service"), "null.service"); + assertEquals(client.getPrincipalTokenCacheKey("coretech", null), "coretech.null"); + } + + @Test + public void testSiaSocketFile() { + SIAClient client = new SIAClient(); + assertEquals(client.siaSocketFile(), "/home/athenz/var/run/sia/sia.ds"); + } + + @Test + public void testLookupPrincipalTokenInCacheNotPresent() { + SIAClient client = new SIAClient(); + + String cacheKey = "coretech.notpresent"; + assertNull(client.lookupPrincipalTokenInCache(cacheKey, null, null)); + } + + @SuppressWarnings("static-access") + @Test + public void testLookupPrincipalTokenInCacheExpired() { + + SIAClient client = new SIAClient(); + + String cacheKey = "coretech.storage"; + PrincipalToken token = new PrincipalToken.Builder("S1", "coretech", "storage") + .issueTime((System.currentTimeMillis() / 1000)).expirationWindow(1000).build(); + client.principalTokenCache.put(cacheKey, token); + + assertNull(client.lookupPrincipalTokenInCache(cacheKey, 3000, 4000)); + assertNull(client.lookupPrincipalTokenInCache(cacheKey, 500, 800)); + + client.principalTokenCache.clear(); + } + + @SuppressWarnings("static-access") + @Test + public void testLookupPrincipalTokenInCache() { + + SIAClient client = new SIAClient(); + + String cacheKey = "coretech.storage"; + PrincipalToken token = new PrincipalToken.Builder("S1", "coretech", "storage") + .issueTime((System.currentTimeMillis() / 1000)).expirationWindow(3500).build(); + client.principalTokenCache.put(cacheKey, token); + + assertNotNull(client.lookupPrincipalTokenInCache(cacheKey, 3000, 4000)); + + client.principalTokenCache.clear(); + } + + @SuppressWarnings("static-access") + @Test + public void testLookupPrincipalTokenInCacheSecondClient() { + + SIAClient client = new SIAClient(); + + String cacheKey = "coretech.storage"; + PrincipalToken token = new PrincipalToken.Builder("S1", "coretech", "storage") + .issueTime((System.currentTimeMillis() / 1000)).expirationWindow(3500).build(); + client.principalTokenCache.put(cacheKey, token); + + assertNotNull(client.lookupPrincipalTokenInCache(cacheKey, 3000, 4000)); + + // now let's get another client + + SIAClient client1 = new SIAClient(); + assertNotNull(client1.lookupPrincipalTokenInCache(cacheKey, 3000, 4000)); + + client.principalTokenCache.clear(); + } + + @Test + public void testProcessRequestDomainList() throws IOException { + + ByteBuffer statusBuf = ByteBuffer.allocate(4); + statusBuf.order(ByteOrder.LITTLE_ENDIAN); + statusBuf.putInt(0); + byte[] status = statusBuf.array(); + + ByteBuffer sizeBuf = ByteBuffer.allocate(4); + sizeBuf.order(ByteOrder.LITTLE_ENDIAN); + sizeBuf.putInt(15); + byte[] size = sizeBuf.array(); + + byte[] list = "domain1,domain2".getBytes(); + + byte[] data = new byte[status.length + size.length + list.length]; + + System.arraycopy(status, 0, data, 0, status.length); + System.arraycopy(size, 0, data, status.length, size.length); + System.arraycopy(list, 0, data, status.length + size.length, list.length); + + InputStream inputStream = new ByteArrayInputStream(data); + OutputStream outputStream = new ByteArrayOutputStream(); + + SIAClient mockSIAClient = Mockito.mock(SIAClient.class); + Mockito.when(mockSIAClient.getSIADomainSocket()).thenReturn(mockSocket); + Mockito.when(mockSocket.getInputStream()).thenReturn(inputStream); + Mockito.when(mockSocket.getOutputStream()).thenReturn(outputStream); + + SIAClient siaClient = new SIAClient(); + String domains = null; + try { + domains = siaClient.processRequest(mockSocket, SIAClient.OP_LIST_DOMAINS, null); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } + assertEquals(domains, "domain1,domain2"); + } + + @Test + public void testProcessRequestPrincipalToken() throws IOException { + + ByteBuffer statusBuf = ByteBuffer.allocate(4); + statusBuf.order(ByteOrder.LITTLE_ENDIAN); + statusBuf.putInt(0); + byte[] status = statusBuf.array(); + + ByteBuffer sizeBuf = ByteBuffer.allocate(4); + sizeBuf.order(ByteOrder.LITTLE_ENDIAN); + sizeBuf.putInt(36); + byte[] size = sizeBuf.array(); + + byte[] token = "v=S1;d=coretech;n=storage;k=0;s=fake".getBytes(); + + byte[] data = new byte[status.length + size.length + token.length]; + + System.arraycopy(status, 0, data, 0, status.length); + System.arraycopy(size, 0, data, status.length, size.length); + System.arraycopy(token, 0, data, status.length + size.length, token.length); + + InputStream inputStream = new ByteArrayInputStream(data); + OutputStream outputStream = new ByteArrayOutputStream(); + + SIAClient mockSIAClient = Mockito.mock(SIAClient.class); + Mockito.when(mockSIAClient.getSIADomainSocket()).thenReturn(mockSocket); + Mockito.when(mockSocket.getInputStream()).thenReturn(inputStream); + Mockito.when(mockSocket.getOutputStream()).thenReturn(outputStream); + + SIAClient siaClient = new SIAClient(); + String ntoken = null; + try { + ntoken = siaClient.processRequest(mockSocket, SIAClient.OP_GET_NTOKEN, "d=coretech,n=storage,e=1800".getBytes()); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } + assertEquals(ntoken, "v=S1;d=coretech;n=storage;k=0;s=fake"); + } + + @SuppressWarnings("static-access") + @Test + public void testGetServicePrincipal() throws IOException { + + SIAClient client = new SIAClient(); + + String cacheKey = "coretech.storage"; + String nToken = "v=S1;d=coretech;n=storage;t=" + (System.currentTimeMillis() / 1000) + + ";e=" + ((System.currentTimeMillis() / 1000) + 3500) + ";k=0;s=fake"; + PrincipalToken token = new PrincipalToken(nToken); + client.principalTokenCache.put(cacheKey, token); + + Principal principal = client.getServicePrincipal("coretech", "storage", null, null, false); + assertNotNull(principal); + assertEquals(principal.getDomain(), "coretech"); + assertEquals(principal.getName(), "storage"); + + client.principalTokenCache.clear(); + } + + @SuppressWarnings("static-access") + @Test + public void testGetServicePrincipalMixedCase() throws IOException { + + SIAClient client = new SIAClient(); + + String cacheKey = "coretech.storage"; + String nToken = "v=S1;d=coretech;n=storage;t=" + (System.currentTimeMillis() / 1000) + + ";e=" + ((System.currentTimeMillis() / 1000) + 3500) + ";k=0;s=fake"; + PrincipalToken token = new PrincipalToken(nToken); + client.principalTokenCache.put(cacheKey, token); + + Principal principal = client.getServicePrincipal("CoreTech", "Storage", null, null, false); + assertNotNull(principal); + assertEquals(principal.getDomain(), "coretech"); + assertEquals(principal.getName(), "storage"); + + client.principalTokenCache.clear(); + } + + @Test (groups = "tokenfileA") + public void testGetServicePrincipalTokenFile() { + SIAClient client = new SIAClient(); + String domain = "athenz"; + String service = "storage"; + // Configured service=athenz.storage + try { + client.getServicePrincipal(domain, service, 500, 3600, false); + } catch (java.io.IOException exc) { + fail("getServicePrincipal"); + } + } + + @Test (groups = "tokenfileA") + public void testGetServicePrincipalTokenFileWrongDomain() { + SIAClient client = new SIAClient(); + String domain = "test.aaa"; + String service = "storage"; + String fullSvc = domain + "." + service; + // Configured service=athenz.storage + try { + client.getServicePrincipal(domain, service, 500, 3600, false); + fail("getServicePrincipal"); + } catch (java.io.IOException exc) { + assertTrue(exc.getMessage().contains("get ntoken from file: Unknown service=" + fullSvc), exc.getMessage()); + } + } + + @Test (groups = "tokenfileA") + public void testGetServicePrincipalTokenFileWrongService() { + SIAClient client = new SIAClient(); + String domain = "athenz"; + String service = "destroyage"; + String fullSvc = domain + "." + service; + // Configured service=athenz.storage + try { + client.getServicePrincipal(domain, service, 500, 3600, false); + fail("getServicePrincipal"); + } catch (java.io.IOException exc) { + assertTrue(exc.getMessage().contains("get ntoken from file: Unknown service=" + fullSvc), exc.getMessage()); + } + } + + @Test (dependsOnGroups = "tokenfileA") + public void testGetServicePrincipalTokenFileEmptyConfigMissingDomainProperty() throws java.io.IOException { + System.setProperty(SIAClient.SIA_PROP_CFG_FILE, "src/test/resources/testcfg_empty.conf"); + try { + SIAClient.initConfigVars(); + SIAClient client = new SIAClient(); + String domain = "athenz"; + String service = "storage"; + // Configured service=athenz.storage + client.getServicePrincipal(domain, service, 500, 3600, false); + fail("getServicePrincipal"); + } catch (java.lang.IllegalArgumentException exc) { + assertTrue(exc.getMessage().contains("SIACLT: invalid ntoken configuration settings"), exc.getMessage()); + } + } + + @Test (dependsOnMethods={"testGetServicePrincipalTokenFileEmptyConfigMissingDomainProperty"}) + public void testGetServicePrincipalTokenFileEmptyConfig() { + System.setProperty(SIAClient.SIA_PROP_NTOKEN_DOMAIN, "athenz"); + System.setProperty(SIAClient.SIA_PROP_CFG_FILE, "src/test/resources/testcfg_empty.conf"); + SIAClient.initConfigVars(); + SIAClient client = new SIAClient(); + String domain = "athenz"; + String service = "storage"; + // Configured service=athenz.storage + try { + client.getServicePrincipal(domain, service, 500, 3600, false); + } catch (java.io.IOException exc) { + fail("getServicePrincipal"); + } + } + + @Test + public void testTokenRequestBuilder() { + SIAClient client = new SIAClient(); + assertEquals("d=test,s=db".getBytes(StandardCharsets.UTF_8), client.tokenRequestBuilder("test", "db", null)); + assertEquals("d=test,s=db,e=100".getBytes(StandardCharsets.UTF_8), client.tokenRequestBuilder("test", "db", new Integer(100))); + assertEquals("d=null,s=db,e=100".getBytes(StandardCharsets.UTF_8), client.tokenRequestBuilder(null, "db", new Integer(100))); + assertEquals("d=test,s=null,e=100".getBytes(StandardCharsets.UTF_8), client.tokenRequestBuilder("test", null, new Integer(100))); + assertEquals("d=null,s=null,e=100".getBytes(StandardCharsets.UTF_8), client.tokenRequestBuilder(null, null, new Integer(100))); + } + + private class SIATestClient extends SIAClient { + + @Override + Socket getSIADomainSocket() throws IOException { + return null; + } + + @Override + String processRequest(Socket sock, int sia_op, byte[] data) throws IOException { + String result = null; + switch (sia_op) { + case SIAClient.OP_LIST_DOMAINS: + result = "test;hoge;athenz"; + break; + case SIAClient.OP_GET_NTOKEN: + result = "v=S1;d=coretech;n=storage;k=0;s=fake"; + break; + } + return result; + } + } + + @Test + public void testGetServicePrincipalNullExpiry() throws IOException { + + // we need to make sure not to use our ntoken path which doesn't + // call the expected get service principal api. so we're going + // to save the value and and then restore it after the test case + + String ntoken_path = SIAClient.cfgNtokenPath; + SIAClient.cfgNtokenPath = null; + + SIAClient client = new SIATestClient(); + + Principal principal = client.getServicePrincipal("coretech", "storage", 500, null, false); + assertNotNull(principal); + assertEquals(principal.getDomain(), "coretech"); + assertEquals(principal.getName(), "storage"); + + // restore the property + + SIAClient.cfgNtokenPath = ntoken_path; + } + + @Test + public void testGetDomainList() throws IOException { + SIAClient client = new SIATestClient(); + ArrayList result = client.getDomainList(); + assertNotNull(result); + assertEquals(result.toString(), "[test, hoge, athenz]"); + } + + @Test + public void testGetServicePrincipalException() throws Exception { + SIAClient client = new SIATestClient(); + + try { + client.getServicePrincipal(null, null, 0, 3600, true); + fail(); + } catch (IOException e) { + } + + SIATestClient.cfgNtokenPath = null; + Principal p = client.getServicePrincipal("test", "hoge", 0, 3600, true); + + assertNotNull(p); + assertEquals(p.getDomain(), "test"); + assertEquals(p.getName(), "hoge"); + + SIATestClient.cfgNtokenPath = "src/test/resources/svc.ntoken"; + } + + @Test + public void testGetFilePrincipalTokenIlligalFile() throws IOException{ + SIAClient client = new SIATestClient(); + + SIATestClient.cfgNtokenPath = "src/test/resources/dummy.ntoken"; + String s = client.getFilePrincipalToken("athenz", "storage"); + + assertEquals(s, ""); + } + + @Test + public void testReadResponseData() throws IOException { + SIAClient client = new SIATestClient(); + + byte[] dummy = { (byte) 0x47, (byte) 0x4e, (byte) 0x54, (byte) 0x2d, (byte) 0x30, (byte) 0x30, (byte) 0x30, + (byte) 0x30 }; + InputStream is = Mockito.mock(InputStream.class); + + Mockito.when(is.read(dummy, 0, dummy.length)).thenReturn(1); + Mockito.when(is.read(dummy, 1, dummy.length - 1)).thenReturn(-1); + + int check = client.readResponseData(is, dummy); + assertEquals(check, 1); + + Mockito.when(is.read(dummy, 0, dummy.length)).thenReturn(-1); + + check = client.readResponseData(is, dummy); + assertEquals(check, 0); + } + + @Test + public void testInitConfigVarsIlligalCFGflag() throws Exception { + + System.clearProperty(SIAClient.SIA_PROP_CFG_FILE); + + SIAClient.initConfigVars(); + assertNull(SIATestClient.cfgNtokenPath); + assertNull(SIATestClient.cfgNtokenDomain); + assertNull(SIATestClient.cfgNtokenService); + } + + @Test + public void testSetConfigVarsIlligal() throws Exception { + + SIAClient.setupConfigVars(null, null, null); + assertNull(SIATestClient.cfgNtokenDomain); + assertNull(SIATestClient.cfgNtokenService); + } +} diff --git a/clients/java/sia/src/test/resources/dummy.ntoken b/clients/java/sia/src/test/resources/dummy.ntoken new file mode 100644 index 00000000000..e69de29bb2d diff --git a/clients/java/sia/src/test/resources/logback.xml b/clients/java/sia/src/test/resources/logback.xml new file mode 100644 index 00000000000..2ce05abfc06 --- /dev/null +++ b/clients/java/sia/src/test/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + System.out + + %d{HH:mm:ss.SSS} [%thread] %-5level %class - %msg%n + + + + + + + + + + diff --git a/clients/java/sia/src/test/resources/svc.ntoken_test b/clients/java/sia/src/test/resources/svc.ntoken_test new file mode 100644 index 00000000000..fae27937dd9 --- /dev/null +++ b/clients/java/sia/src/test/resources/svc.ntoken_test @@ -0,0 +1 @@ +v=S1;d=athenz;n=storage;t=55555;e=55555;s=fake diff --git a/clients/java/sia/src/test/resources/testcfg.conf b/clients/java/sia/src/test/resources/testcfg.conf new file mode 100644 index 00000000000..e2f1d311202 --- /dev/null +++ b/clients/java/sia/src/test/resources/testcfg.conf @@ -0,0 +1,6 @@ +{ + "ntoken_path": "/map/to/the/stars", + "ntoken_domain": "athenz", + "ntoken_service": "how_much_to_tip" +} + diff --git a/clients/java/sia/src/test/resources/testcfg_empty.conf b/clients/java/sia/src/test/resources/testcfg_empty.conf new file mode 100644 index 00000000000..b23ba173dfd --- /dev/null +++ b/clients/java/sia/src/test/resources/testcfg_empty.conf @@ -0,0 +1,6 @@ +{ + "ntoken_path": "", + "ntoken_domain": "", + "ntoken_service": "" +} + diff --git a/clients/java/zms/README.md b/clients/java/zms/README.md new file mode 100644 index 00000000000..7f438730bc9 --- /dev/null +++ b/clients/java/zms/README.md @@ -0,0 +1,23 @@ +zms-java-client +=============== + +A Java client library to access the ZMS server. +The client library encapsulates the stub generated from the ZMS RDL. +It includes zms-core and all other dependencies. + +--- Connection Timeouts --- + +Default read and connect timeout values for ZMS Client connections +are 30000ms (30sec). The application can change these values by using +the following system properties: + + * athenz.zms.client.read_timeout + * athenz.zms.client.connect_timeout + +The values specified for timeouts must be in milliseconds. + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/clients/java/zms/pom.xml b/clients/java/zms/pom.xml new file mode 100644 index 00000000000..fe6730a43c2 --- /dev/null +++ b/clients/java/zms/pom.xml @@ -0,0 +1,227 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../../pom.xml + + + zms_java_client + jar + zms-java-client + ZMS Java Client Library + + + + ${project.groupId} + zms_core + ${project.parent.version} + + + ${project.groupId} + client_common + ${project.parent.version} + + + ${project.groupId} + auth_core + ${project.parent.version} + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey.version} + + + com.fasterxml.jackson.core + jackson-annotations + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + org.glassfish.jersey.core + jersey-client + ${jersey.version} + + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.1.1 + + + + exec + + generate-sources + + + + bash + + scripts/make_stubs.sh + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + prepare-package + + jar + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + + + + + org.glassfish.hk2 + org.glassfish.hk2.external + org.glassfish.jersey + org.glassfish.jersey.ext + org.glassfish.jersey.core + org.glassfish.jersey.media + org.glassfish.jersey.bundles.repackaged + com.fasterxml.jackson.core + com.fasterxml.jackson.jaxrs + com.fasterxml.jackson.module + javax.ws.rs + javax.annotation + javax.inject + jersey.repackaged.com.google.common + org.aopalliance + + + + + javax.inject + athenz.shade.zms.javax.inject + + + org.aopalliance + athenz.shade.zms.org.aopalliance + + + jersey.repackaged.com.google.common + athenz.shade.zms.jersey.repackaged.com.google.common + + + javax.ws.rs + athenz.shade.zms.javax.ws.rs + + + javax.annotation + athenz.shade.zms.javax.annotation + + + org.glassfish.jersey + athenz.shade.zms.org.glassfish.jersey + + + org.glassfish.hk2 + athenz.shade.zms.org.glassfish.hk2 + + + org.glassfish.hk2.external + athenz.shade.zms.org.glassfish.hk2.external + + + org.jvnet.hk2 + athenz.shade.zms.org.jvnet.hk2 + + + org.jvnet.tiger_types + athenz.shade.zms.org.jvnet.tiger_types + + + com.fasterxml.jackson + athenz.shade.zms.com.fasterxml.jackson + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + + + coverage + + + coverage + + + + + + com.atlassian.maven.plugins + maven-clover2-plugin + 4.0.4 + + 1.8 + ${HOME}/license/clover.license + + + **/ZMSRDLGeneratedClient.java + + + + + + + + + diff --git a/clients/java/zms/scripts/make_stubs.sh b/clients/java/zms/scripts/make_stubs.sh new file mode 100755 index 00000000000..969f6049a07 --- /dev/null +++ b/clients/java/zms/scripts/make_stubs.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# If the zms_core dependency has been updated, then this script should be run +# manually to pick up the latest rdl to generate the appropriate client library + +# Note this script is dependent on the rdl utility. +# go get github.com/ardielle/ardielle-tools/... + +command -v rdl >/dev/null 2>&1 || { + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "SOURCE WARNING"; + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "Please install rdl utility: go get github.com/ardielle/ardielle-tools/..."; + echo >&2 "Skipping source generation..."; + exit 0; +} + +RDL_FILE=../../../core/zms/src/main/rdl/ZMS.rdl + +echo "Generate the client library..." +rdl -s generate -o src/main/java -x clientclass=ZMSRDLGenerated java-client $RDL_FILE + +# Copyright 2016 Yahoo Inc. +# Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. diff --git a/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ResourceError.java b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ResourceError.java new file mode 100644 index 00000000000..231c7abdf9d --- /dev/null +++ b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ResourceError.java @@ -0,0 +1,24 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zms; + +public class ResourceError { + + public int code; + public String message; + + public ResourceError code(int code) { + this.code = code; + return this; + } + public ResourceError message(String message) { + this.message = message; + return this; + } + + public String toString() { + return "{code: " + code + ", message: \"" + message + "\"}"; + } + +} diff --git a/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ResourceException.java b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ResourceException.java new file mode 100644 index 00000000000..a76e476ac22 --- /dev/null +++ b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ResourceException.java @@ -0,0 +1,79 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zms; + +public class ResourceException extends RuntimeException { + public final static int OK = 200; + public final static int CREATED = 201; + public final static int ACCEPTED = 202; + public final static int NO_CONTENT = 204; + public final static int MOVED_PERMANENTLY = 301; + public final static int FOUND = 302; + public final static int SEE_OTHER = 303; + public final static int NOT_MODIFIED = 304; + public final static int TEMPORARY_REDIRECT = 307; + public final static int BAD_REQUEST = 400; + public final static int UNAUTHORIZED = 401; + public final static int FORBIDDEN = 403; + public final static int NOT_FOUND = 404; + public final static int CONFLICT = 409; + public final static int GONE = 410; + public final static int PRECONDITION_FAILED = 412; + public final static int UNSUPPORTED_MEDIA_TYPE = 415; + public final static int INTERNAL_SERVER_ERROR = 500; + public final static int NOT_IMPLEMENTED = 501; + + public final static int SERVICE_UNAVAILABLE = 503; + + public static String codeToString(int code) { + switch (code) { + case OK: return "OK"; + case CREATED: return "Created"; + case ACCEPTED: return "Accepted"; + case NO_CONTENT: return "No Content"; + case MOVED_PERMANENTLY: return "Moved Permanently"; + case FOUND: return "Found"; + case SEE_OTHER: return "See Other"; + case NOT_MODIFIED: return "Not Modified"; + case TEMPORARY_REDIRECT: return "Temporary Redirect"; + case BAD_REQUEST: return "Bad Request"; + case UNAUTHORIZED: return "Unauthorized"; + case FORBIDDEN: return "Forbidden"; + case NOT_FOUND: return "Not Found"; + case CONFLICT: return "Conflict"; + case GONE: return "Gone"; + case PRECONDITION_FAILED: return "Precondition Failed"; + case UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type"; + case INTERNAL_SERVER_ERROR: return "Internal Server Error"; + case NOT_IMPLEMENTED: return "Not Implemented"; + default: return "" + code; + } + } + + int code; + Object data; + + public ResourceException(int code) { + this(code, new ResourceError().code(code).message(codeToString(code))); + } + + public ResourceException(int code, Object data) { + super("ResourceException (" + code + "): " + data); + this.code = code; + this.data = data; + } + + public int getCode() { + return code; + } + + public Object getData() { + return data; + } + + public T getData(Class cl) { + return cl.cast(data); + } + +} diff --git a/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSAuthorizer.java b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSAuthorizer.java new file mode 100644 index 00000000000..c503167f969 --- /dev/null +++ b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSAuthorizer.java @@ -0,0 +1,139 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import java.io.Closeable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authorizer; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.PrincipalAuthority; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.token.PrincipalToken; + +public class ZMSAuthorizer implements Authorizer, Closeable { + + String endpoint = null; + String serviceDomain = null; + protected ZMSClient client = null; + private static final Logger LOGGER = LoggerFactory.getLogger(ZMSAuthorizer.class); + private static final PrincipalAuthority PRINCIPAL_AUTHORITY = new PrincipalAuthority(); + + /** + * Constructs a new ZMSAuthorizer object with the given resource service domain + * name. The url for ZMS Server is automatically retrieved from the athenz + * configuration file (zms_url field). + * @param serviceDomain resource service domain name + */ + public ZMSAuthorizer(String serviceDomain) { + this(null, serviceDomain); + } + + /** + * Constructs a new ZMSAuthorizer object with the given ZMS Server endpoint and + * given resource service domain name + * @param endpoint ZMS Server url (e.g. http://server.athenzcompany.com:4443/zms/v1) + * @param serviceDomain resource service domain name + */ + public ZMSAuthorizer(String endpoint, String serviceDomain) { + this.endpoint = endpoint; + this.serviceDomain = serviceDomain; + client = new ZMSClient(this.endpoint); + } + + /** + * Close the ZMS Client object + */ + public void close() { + if (client != null) { + client.close(); + client = null; + } + } + + /** + * Set the authorizer to use the specified zms client object + * @param client ZMSClient object to use for authorization checks + */ + public void setZMSClient(ZMSClient client) { + // if we already have a client then we need to close it + close(); + this.client = client; + } + + /** + * Requests the ZMS to indicate whether or not the specific request for the + * specified resource with authentication details will be granted or not. + * @param action value of the action to be carried out (e.g. "UPDATE", "DELETE") + * @param resource resource value + * @param principalToken principal token (NToken) that will be authenticated and checked for + * requested access + * @param trustDomain (optional - usually null) if the access checks involves cross + * domain check only check the specified trusted domain and ignore all others + * @return boolean indicating whether or not the request will be granted or not + */ + public boolean access(String action, String resource, String principalToken, String trustDomain) { + PrincipalToken token = new PrincipalToken(principalToken); + Principal principal = SimplePrincipal.create(token.getDomain(), token.getName(), + token.getSignedToken(), 0, PRINCIPAL_AUTHORITY); + return access(action, resource, principal, trustDomain); + } + + /** + * Requests the ZMS to indicate whether or not the specific request for the + * specified resource with authentication details will be granted or not. + * @param action value of the action to be carried out (e.g. "UPDATE", "DELETE") + * @param resource resource value + * @param principal principal object that will be authenticated and checked for + * requested access + * @param trustDomain (optional - usually null) if the access checks involves cross + * domain check only check the specified trusted domain and ignore all others + * @return boolean indicating whether or not the request will be granted or not + */ + public boolean access(String action, String resource, Principal principal, String trustDomain) { + + //the "resource" may be an entity name here, we need a full resource name + + String rn = (resource.contains(":")) ? resource : serviceDomain + ":" + resource; + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("ZMSAuthorizer.access(" + action + ", " + rn + ", " + principal.getYRN() + + ", " + trustDomain + ")"); + } + + try { + client.addCredentials(principal); + return client.getAccess(action, rn, trustDomain).getGranted(); + } catch (ZMSClientException e) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("ZMSAuthorizer.access: " + e); + } + + switch (e.getCode()) { + case ZMSClientException.NOT_FOUND: + throw new ZMSClientException(ZMSClientException.FORBIDDEN, "Not found: " + rn); + default: + throw e; + } + } catch (Throwable th) { + th.printStackTrace(); + throw new ZMSClientException(ZMSClientException.FORBIDDEN, "Cannot contact ZMS"); + } + } +} diff --git a/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClient.java b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClient.java new file mode 100644 index 00000000000..18c29015e0c --- /dev/null +++ b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClient.java @@ -0,0 +1,1691 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.io.Closeable; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Map; +import java.util.List; +import java.util.Date; + +import org.glassfish.jersey.client.ClientProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.PrincipalAuthority; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.token.PrincipalToken; +import com.yahoo.athenz.common.config.AthenzConfig; +import com.yahoo.rdl.JSON; + +public class ZMSClient implements Closeable { + + private String zmsUrl = null; + private Principal principal = null; + private boolean principalCheckDone = false; + protected ZMSRDLGeneratedClient client = null; + + private static final String STR_ENV_ROOT = "ROOT"; + private static final String STR_DEF_ROOT = "/home/athenz"; + private static final String HTTP_RFC1123_DATE_FORMAT = "EEE, d MMM yyyy HH:mm:ss zzz"; + + public static final String ZMS_CLIENT_PROP_ATHENZ_CONF = "athenz.athenz_conf"; + public static final String ZMS_CLIENT_PROP_READ_TIMEOUT = "athenz.zms.client.read_timeout"; + public static final String ZMS_CLIENT_PROP_CONNECT_TIMEOUT = "athenz.zms.client.connect_timeout"; + + private static final Logger LOGGER = LoggerFactory.getLogger(ZMSClient.class); + private static final Authority PRINCIPAL_AUTHORITY = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + + /** + * Constructs a new ZMSClient object with media type set to application/json. + * The url for ZMS Server is automatically retrieved from the athenz_config + * package's configuration file (zms_url field). The client can only be used + * to retrieve objects from ZMS that do not require any authentication + * otherwise addCredentials method must be used to set the principal identity. + * Default read and connect timeout values are 30000ms (30sec). The application can + * change these values by using the yahoo.zms_java_client.read_timeout and + * yahoo.zms_java_client.connect_timeout system properties. The values specified + * for timeouts must be in milliseconds. + */ + public ZMSClient() { + initClient(null); + } + + /** + * Constructs a new ZMSClient object with the given ZMS Server url and + * media type set to application/json. The client can only be used + * to retrieve objects from ZMS that do not require any authentication + * otherwise addCredentials method must be used to set the principal identity. + * Default read and connect timeout values are 30000ms (30sec). The application can + * change these values by using the yahoo.zms_java_client.read_timeout and + * yahoo.zms_java_client.connect_timeout system properties. The values specified + * for timeouts must be in milliseconds. + * @param url ZMS Server url (e.g. https://server1.athenzcompany.com:4443/zms/v1) + */ + public ZMSClient(String url) { + initClient(url); + } + + /** + * Close the ZMSClient object and release any allocated resources. + */ + public void close() { + client.close(); + } + + public void setZMSRDLGeneratedClient(ZMSRDLGeneratedClient client) { + this.client = client; + } + + /** + * Sets or overrides the current principal identity set in the client. + * @param identity Principal identity for authenticating requests + * @return self ZMSClient object + */ + public ZMSClient addCredentials(Principal identity) { + + // make sure the principal has proper authority assigned + + if (identity == null || identity.getAuthority() == null) { + throw new IllegalArgumentException("Principal must be valid object with authority field"); + } + + // if we already have a principal set, we're going to + // clear our credentials first + + if (principal != null) { + client.addCredentials(principal.getAuthority().getHeader(), null); + } + + // now we're going to update our principal and set credentials + + principal = identity; + principalCheckDone = false; + + final Authority authority = principal.getAuthority(); + if (authority != null) { + client.addCredentials(authority.getHeader(), principal.getCredentials()); + + // finally check if we this is a principal token required by ZMS + + principalCheckDone = (authority instanceof PrincipalAuthority); + } + + return this; + } + + /** + * Clear the principal identity set for the client. Unless a new principal is set + * using the addCredentials method, the client can only be used to requests data + * from the ZMS Server that doesn't require any authentication. + * @return self ZMSClient object + */ + public ZMSClient clearCredentials() { + if (principal != null) { + client.addCredentials(principal.getAuthority().getHeader(), null); + principal = null; + principalCheckDone = true; + } + return this; + } + + /** + * If the current principal is the user principal then request + * a UserToken from ZMS and set the UserToken as the principal + * identity for authentication. + */ + private void updatePrincipal() { + + /* if the check has already been done then we have nothing to do */ + + if (principalCheckDone) { + return; + } + + /* make sure we have a principal specified */ + + if (principal == null) { + principalCheckDone = true; + return; + } + + /* so at this point we have some credentials specified + * but it's not the principal authority so we're going + * to ask ZMS to return a UserToken for us. + */ + + String userName = principal.getName(); + long issueTime = principal.getIssueTime(); + UserToken userToken = getUserToken(userName); + + PrincipalToken principalToken = new PrincipalToken(userToken.getToken()); + Principal identity = SimplePrincipal.create(principalToken.getDomain(), + principalToken.getName(), userToken.getToken(), issueTime, + PRINCIPAL_AUTHORITY); + clearCredentials(); + addCredentials(identity); + } + + String lookupZMSUrl() { + + String rootDir = System.getenv(STR_ENV_ROOT); + if (rootDir == null) { + rootDir = STR_DEF_ROOT; + } + + String confFileName = System.getProperty(ZMS_CLIENT_PROP_ATHENZ_CONF, + rootDir + "/conf/athenz/athenz.conf"); + String url = null; + try { + Path path = Paths.get(confFileName); + AthenzConfig conf = JSON.fromBytes(Files.readAllBytes(path), AthenzConfig.class); + url = conf.getZmsUrl(); + } catch (Exception ex) { + LOGGER.error("Unable to extract ZMS Url from {} exc: {}", + confFileName, ex.getMessage()); + } + + return url; + } + /** + * Initialize the client for class constructors + * @param url ZMS Server url + * @param identity Principal identity for authentication + * @param mediaType media type for http content type + */ + private void initClient(String url) { + + /* if we have no url specified then we're going to retrieve + * the value from our configuration package */ + + if (url == null) { + zmsUrl = lookupZMSUrl(); + } else { + zmsUrl = url; + } + + /* verify if the url is ending with /zms/v1 and if it's + * not we'll automatically append it */ + + if (zmsUrl != null && !zmsUrl.isEmpty()) { + if (!zmsUrl.endsWith("/zms/v1")) { + if (zmsUrl.charAt(zmsUrl.length() - 1) != '/') { + zmsUrl += '/'; + } + zmsUrl += "zms/v1"; + } + } + + /* determine our read and connect timeouts */ + + int readTimeout = Integer.parseInt(System.getProperty(ZMS_CLIENT_PROP_READ_TIMEOUT, "30000")); + int connectTimeout = Integer.parseInt(System.getProperty(ZMS_CLIENT_PROP_CONNECT_TIMEOUT, "30000")); + + /* if we are not given a url then use the default value */ + + client = new ZMSRDLGeneratedClient(zmsUrl) + .setProperty(ClientProperties.CONNECT_TIMEOUT, connectTimeout) + .setProperty(ClientProperties.READ_TIMEOUT, readTimeout); + } + + public String getZmsUrl() { + return zmsUrl; + } + + /** + * Generate a role name as expected by ZMS Server can be used to + * set the role object's name field (e.g. role.setName(name)) + * @param domain name of the domain + * @param role name of the role + * @return full role name + */ + public String generateRoleName(String domain, String role) { + StringBuilder str = new StringBuilder(256); + str.append(domain); + str.append(":role."); + str.append(role); + return str.toString(); + } + + /** + * Generate a policy name as expected by ZMS Server can be used to + * set the policy object's name field (e.g. policy.setName(name)) + * @param domain name of the domain + * @param policy name of the policy + * @return full policy name + */ + public String generatePolicyName(String domain, String policy) { + StringBuilder str = new StringBuilder(256); + str.append(domain); + str.append(":policy."); + str.append(policy); + return str.toString(); + } + + /** + * Generate a service name as expected by ZMS Server can be used to + * set the service identity object's name field + * (e.g. serviceIdentity.setName(name)) + * @param domain name of the domain + * @param service name of the service + * @return full service identity name + */ + public String generateServiceIdentityName(String domain, String service) { + StringBuilder str = new StringBuilder(256); + str.append(domain); + str.append("."); + str.append(service); + return str.toString(); + } + + /** + * Retrieve the specified domain object + * @param domain name of the domain to be retrieved + * @return Domain object or ZMSClientException will be thrown in case of failure + */ + public Domain getDomain(String domain) { + updatePrincipal(); + try { + return client.getDomain(domain); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of domains provisioned on the ZMS Server + * @return list of Domains or ZMSClientException will be thrown in case of failure + */ + public DomainList getDomainList() { + return getDomainList(null, null, null, null, null, null, null); + } + + /** + * Retrieve the list of domains provisioned on the ZMS Server + * filters based on the specified arguments + * @param limit number of domain objects to return + * @param skip exclude all the domains including the specified one from the return set + * @param prefix return domains starting with this value + * @param depth maximum depth of the domain (0 - top level domains only) + * @param account return domain that has the specified account name. If account name + * is specified all other optional attributes are ignored since there must be + * only one domain matching the specified account name. + * @param productId return domain that has the specified product id. If product id + * is specified all other optional attributes are ignored since there must be + * only one domain matching the specified product id. + * @param modifiedSince return domains only modified since this date + * @return list of domain names or ZMSClientException will be thrown in case of failure + */ + public DomainList getDomainList(Integer limit, String skip, String prefix, Integer depth, + String account, Integer productId, Date modifiedSince) { + updatePrincipal(); + String modSinceStr = null; + if (modifiedSince != null) { + DateFormat df = new SimpleDateFormat(HTTP_RFC1123_DATE_FORMAT); + modSinceStr = df.format(modifiedSince); + } + try { + return client.getDomainList(limit, skip, prefix, depth, account, productId, null, null, modSinceStr); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of domains provisioned on the ZMS Server + * filters based on the specified arguments + * @param roleMember name of the principal + * @param roleName name of the role where the principal is a member of + * @return list of domain names or ZMSClientException will be thrown in case of failure + */ + public DomainList getDomainList(String roleMember, String roleName) { + updatePrincipal(); + try { + return client.getDomainList(null, null, null, null, null, null, roleMember, roleName, null); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Create/Update Top level domain. If updating a domain the provided + * object must contain all attributes as it will replace the full domain + * object configured on the server (not just some of the attributes). + * @param auditRef string containing audit specification or ticket number + * @param detail TopLevelDomain object to be created in ZMS + * @return created Domain object or ZMSClientException will be thrown in case of failure + */ + public Domain postTopLevelDomain(String auditRef, TopLevelDomain detail) { + updatePrincipal(); + try { + return client.postTopLevelDomain(auditRef, detail); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Create/Update a sub-domain in the specified domain. If updating a + * subdomain the provided object must contain all attributes as it will + * replace the full domain object configured on the server (not just some + * of the attributes). + * @param parent name of the parent domain + * @param auditRef string containing audit specification or ticket number + * @param detail SubDomain object to be created in ZMS + * @return created Domain object or ZMSClientException will be thrown in case of failure + */ + public Domain postSubDomain(String parent, String auditRef, SubDomain detail) { + updatePrincipal(); + try { + return client.postSubDomain(parent, auditRef, detail); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Create a top-level user-domain - this is user.<userid> domain. + * @param name domain to be created, this is the <userid> + * @param auditRef string containing audit specification or ticket number + * @param detail UserDomain object to be created in ZMS + * @return created Domain object or ZMSClientException will be thrown in case of failure + */ + public Domain postUserDomain(String name, String auditRef, UserDomain detail) { + updatePrincipal(); + try { + return client.postUserDomain(name, auditRef, detail); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete a top level domain + * @param name domain name to be deleted from ZMS + * @param auditRef string containing audit specification or ticket number + */ + public void deleteTopLevelDomain(String name, String auditRef) { + updatePrincipal(); + try { + client.deleteTopLevelDomain(name, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete a sub-domain + * @param parent name of the parent domain + * @param name sub-domain to be deleted + * @param auditRef string containing audit specification or ticket number + */ + public void deleteSubDomain(String parent, String name, String auditRef) { + updatePrincipal(); + try { + client.deleteSubDomain(parent, name, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete a top-level user-domain (user.<userid>) + * @param name domain to be deleted, this is the <userid> + * @param auditRef string containing audit specification or ticket number + */ + public void deleteUserDomain(String name, String auditRef) { + updatePrincipal(); + try { + client.deleteUserDomain(name, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Set the domain meta parameters + * @param name domain name to be modified + * @param auditRef string containing audit specification or ticket number + * @param detail meta parameters to be set on the domain + */ + public void putDomainMeta(String name, String auditRef, DomainMeta detail) { + updatePrincipal(); + try { + client.putDomainMeta(name, auditRef, detail); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of roles defined for the specified domain + * @param domainName name of the domain + * @return list of role names or ZMSClientException will be thrown in case of failure + */ + public RoleList getRoleList(String domainName) { + updatePrincipal(); + try { + return client.getRoleList(domainName, null, null); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of roles defined for the specified domain + * filtered based on the parameters specified + * @param domainName name of the domain + * @param limit number of roles to return + * @param skip exclude all the roles including the specified one from the return set + * @return list of role names or ZMSClientException will be thrown in case of failure + */ + public RoleList getRoleList(String domainName, Integer limit, String skip) { + updatePrincipal(); + try { + return client.getRoleList(domainName, limit, skip); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of roles defined for the specified domain. The roles + * will contain their attributes and, if specified, the list of members. + * @param domainName name of the domain + * @param members include all members for group roles as well + * @return list of roles or ZMSClientException will be thrown in case of failure + */ + public Roles getRoles(String domainName, Boolean members) { + updatePrincipal(); + try { + return client.getRoles(domainName, members); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the specified role + * @param domainName name of the domain + * @param roleName name of the role + * @return role object or ZMSClientException will be thrown in case of failure + */ + public Role getRole(String domainName, String roleName) { + return getRole(domainName, roleName, false, false); + } + + /** + * Retrieve the specified role + * @param domainName name of the domain + * @param roleName name of the role + * @param auditLog include audit log for the role changes in the response + * @return role object or ZMSClientException will be thrown in case of failure + */ + public Role getRole(String domainName, String roleName, boolean auditLog) { + return getRole(domainName, roleName, auditLog, false); + } + + + /** + * Retrieve the specified role + * @param domainName name of the domain + * @param roleName name of the role + * @param auditLog include audit log for the role changes in the response + * @param expand if the requested role is a delegated/trust role, this flag + * will instruct the ZMS server to automatically retrieve the members of the + * role from the delegated domain and return as part of the role object + * @return role object or ZMSClientException will be thrown in case of failure + */ + public Role getRole(String domainName, String roleName, boolean auditLog, boolean expand) { + updatePrincipal(); + try { + return client.getRole(domainName, roleName, auditLog, expand); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Create/Update a new role in the specified domain. If updating a role + * the provided object must contain all attributes as it will replace + * the full role object configured on the server (not just some of the attributes). + * @param domainName name of the domain + * @param roleName name of the role + * @param auditRef string containing audit specification or ticket number + * @param role role object to be added to the domain + */ + public void putRole(String domainName, String roleName, String auditRef, Role role) { + updatePrincipal(); + try { + client.putRole(domainName, roleName, auditRef, role); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete the specified role from domain + * @param domainName name of the domain + * @param roleName name of the role + * @param auditRef string containing audit specification or ticket number + */ + public void deleteRole(String domainName, String roleName, String auditRef) { + updatePrincipal(); + try { + client.deleteRole(domainName, roleName, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Get membership details for the specified member in the given role + * in a specified domain + * @param domainName name of the domain + * @param roleName name of the role + * @param memberName name of the member + * @return Membership object or ZMSClientException will be thrown in case of failure + */ + public Membership getMembership(String domainName, String roleName, String memberName) { + updatePrincipal(); + try { + return client.getMembership(domainName, roleName, memberName); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Add a new member in the specified role. + * @param domainName name of the domain + * @param roleName name of the role + * @param memberName name of the member to be added + * @param auditRef string containing audit specification or ticket number + */ + public void putMembership(String domainName, String roleName, String memberName, String auditRef) { + updatePrincipal(); + Membership mbr = new Membership(); + mbr.setRoleName(roleName); + mbr.setMemberName(memberName); + mbr.setIsMember(true); + try { + client.putMembership(domainName, roleName, memberName, auditRef, mbr); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Remove the specified member from the role + * @param domainName name of the domain + * @param roleName name of the role + * @param memberName name of the member to be removed + * @param auditRef string containing audit specification or ticket number + */ + public void deleteMembership(String domainName, String roleName, String memberName, String auditRef) { + updatePrincipal(); + try { + client.deleteMembership(domainName, roleName, memberName, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of policies defined for the specified domain. The policies + * will contain their attributes and, if specified, the list of assertions. + * @param domainName name of the domain + * @param assertions include all assertion for policies as well + * @return list of policies or ZMSClientException will be thrown in case of failure + */ + public Policies getPolicies(String domainName, Boolean assertions) { + updatePrincipal(); + try { + return client.getPolicies(domainName, assertions); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Get list of policies defined in the specified domain + * @param domainName name of the domain + * @return list of policy names or ZMSClientException will be thrown in case of failure + */ + public PolicyList getPolicyList(String domainName) { + updatePrincipal(); + try { + return client.getPolicyList(domainName, null, null); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Get list of policies defined in the specified domain filtered + * based on the specified arguments + * @param domainName name of the domain + * @param limit number of policies to return + * @param skip exclude all the policies including the specified one from the return set + * @return list of policy names or ZMSClientException will be thrown in case of failure + */ + public PolicyList getPolicyList(String domainName, Integer limit, String skip) { + updatePrincipal(); + try { + return client.getPolicyList(domainName, limit, skip); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Return the specified policy object assertion + * @param domainName name of the domain + * @param policyName name of the policy + * @param assertionId the id of the assertion to be retrieved + * @return Assertion object or ZMSClientException will be thrown in case of failure + */ + public Assertion getAssertion(String domainName, String policyName, Long assertionId) { + updatePrincipal(); + try { + return client.getAssertion(domainName, policyName, assertionId); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Add the specified assertion to the specified policy + * @param domainName name of the domain + * @param policyName name of the policy + * @param auditRef string containing audit specification or ticket number + * @param assertion Assertion object to be added to the policy + * @return updated assertion object that includes the server assigned id + */ + public Assertion putAssertion(String domainName, String policyName, String auditRef, Assertion assertion) { + updatePrincipal(); + try { + return client.putAssertion(domainName, policyName, auditRef, assertion); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete specified assertion from the given policy + * @param domainName name of the domain + * @param policyName name of the policy + * @param assertionId the id of the assertion to be deleted + * @param auditRef string containing audit specification or ticket number + */ + public void deleteAssertion(String domainName, String policyName, Long assertionId, String auditRef) { + updatePrincipal(); + try { + client.deleteAssertion(domainName, policyName, assertionId, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Return the specified policy object + * @param domainName name of the domain + * @param policyName name of the policy to be retrieved + * @return Policy object or ZMSClientException will be thrown in case of failure + */ + public Policy getPolicy(String domainName, String policyName) { + updatePrincipal(); + try { + return client.getPolicy(domainName, policyName); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Create/Update a new policy in the specified domain. If updating a policy + * the provided object must contain all attributes as it will replace the + * full policy object configured on the server (not just some of the attributes). + * @param domainName name of the domain + * @param policyName name of the policy + * @param auditRef string containing audit specification or ticket number + * @param policy Policy object with details + */ + public void putPolicy(String domainName, String policyName, String auditRef, Policy policy) { + updatePrincipal(); + try { + client.putPolicy(domainName, policyName, auditRef, policy); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete specified policy from a domain + * @param domainName name of the domain + * @param policyName name of the policy to be deleted + * @param auditRef string containing audit specification or ticket number + */ + public void deletePolicy(String domainName, String policyName, String auditRef) { + updatePrincipal(); + try { + client.deletePolicy(domainName, policyName, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Create/Update a new service in the specified domain. If updating a service + * the provided object must contain all attributes as it will replace the + * full service object configured on the server (not just some of the attributes). + * @param domainName name of the domain + * @param serviceName name of the service + * @param auditRef string containing audit specification or ticket number + * @param service ServiceIdentity object with all service details + */ + public void putServiceIdentity(String domainName, String serviceName, + String auditRef, ServiceIdentity service) { + updatePrincipal(); + try { + client.putServiceIdentity(domainName, serviceName, auditRef, service); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the specified service object from a domain + * @param domainName name of the domain + * @param serviceName name of the service to be retrieved + * @return ServiceIdentity object or ZMSClientException will be thrown in case of failure + */ + public ServiceIdentity getServiceIdentity(String domainName, String serviceName) { + updatePrincipal(); + try { + return client.getServiceIdentity(domainName, serviceName); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete the specified service from a domain + * @param domainName name of the domain + * @param serviceName name of the service to be deleted + * @param auditRef string containing audit specification or ticket number + */ + public void deleteServiceIdentity(String domainName, String serviceName, String auditRef) { + updatePrincipal(); + try { + client.deleteServiceIdentity(domainName, serviceName, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of services defined for the specified domain. The services + * will contain their attributes and, if specified, the list of publickeys and hosts. + * @param domainName name of the domain + * @param publicKeys include all public keys for services as well + * @param hosts include all configured hosts for services as well + * @return list of services or ZMSClientException will be thrown in case of failure + */ + public ServiceIdentities getServiceIdentities(String domainName, Boolean publicKeys, Boolean hosts) { + updatePrincipal(); + try { + return client.getServiceIdentities(domainName, publicKeys, hosts); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the full list of services defined in a domain + * @param domainName name of the domain + * @return list of all service names or ZMSClientException will be thrown in case of failure + */ + public ServiceIdentityList getServiceIdentityList(String domainName) { + updatePrincipal(); + try { + return client.getServiceIdentityList(domainName, null, null); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of services defined in a domain filtered + * based on the specified arguments + * @param domainName name of the domain + * @param limit number of services to return + * @param skip exclude all the services including the specified one from the return set + * @return list of service names or ZMSClientException will be thrown in case of failure + */ + public ServiceIdentityList getServiceIdentityList(String domainName, Integer limit, String skip) { + updatePrincipal(); + try { + return client.getServiceIdentityList(domainName, limit, skip); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the specified public key from the given service object + * @param domainName name of the domain + * @param serviceName name of the service + * @param keyId the identifier of the public key to be retrieved + * @return PublicKeyEntry object or ZMSClientException will be thrown in case of failure + */ + public PublicKeyEntry getPublicKeyEntry(String domainName, String serviceName, String keyId) { + updatePrincipal(); + try { + return client.getPublicKeyEntry(domainName, serviceName, keyId); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Update or add (if doesn't already exist) the specified public key in the service object + * @param domainName name of the domain + * @param serviceName name of the service + * @param keyId the identifier of the public key to be updated + * @param auditRef string containing audit specification or ticket number + * @param publicKeyEntry that contains the public key details + */ + public void putPublicKeyEntry(String domainName, String serviceName, String keyId, String auditRef, + PublicKeyEntry publicKeyEntry) { + updatePrincipal(); + try { + client.putPublicKeyEntry(domainName, serviceName, keyId, auditRef, publicKeyEntry); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete the specified public key from the service object. If the key doesn't exist then + * it is treated as a successful operation and no exception will be thrown. + * @param domainName name of the domain + * @param serviceName name of the service + * @param keyId the identifier of the public key to be deleted + * @param auditRef string containing audit specification or ticket number + */ + public void deletePublicKeyEntry(String domainName, String serviceName, String keyId, String auditRef) { + updatePrincipal(); + try { + client.deletePublicKeyEntry(domainName, serviceName, keyId, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Create/update an entity object in ZMS + * @param domainName name of the domain + * @param entityName name of the entity + * @param auditRef string containing audit specification or ticket number + * @param entity entity object with details + */ + public void putEntity(String domainName, String entityName, String auditRef, Entity entity) { + updatePrincipal(); + try { + client.putEntity(domainName, entityName, auditRef, entity); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the specified entity from the ZMS Server + * @param domainName name of the domain + * @param entityName name of the entity + * @return Entity object with details or ZMSClientException will be thrown in case of failure + */ + public Entity getEntity(String domainName, String entityName) { + updatePrincipal(); + try { + return client.getEntity(domainName, entityName); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete the specified entity from the ZMS Server + * @param domainName name of the domain + * @param entityName name of the entity + * @param auditRef string containing audit specification or ticket number + */ + public void deleteEntity(String domainName, String entityName, String auditRef) { + updatePrincipal(); + try { + client.deleteEntity(domainName, entityName, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of entities defined for the specified domain + * @param domainName name of the domain + * @return list of entity names or ZMSClientException will be thrown in case of failure + */ + public EntityList getEntityList(String domainName) { + updatePrincipal(); + try { + return client.getEntityList(domainName); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Create a new tenant for the specified domain. The service specifies the + * provider. + * @param tenantDomain name of the tenant domain + * @param providerService name of the provider service + * format: provider-domain-name.provider-service-name, ex: "sports.storage" + * @param auditRef string containing audit specification or ticket number + * @param tenant Tenancy object with tenant details + */ + public void putTenancy(String tenantDomain, String providerService, String auditRef, Tenancy tenant) { + updatePrincipal(); + try { + client.putTenancy(tenantDomain, providerService, auditRef, tenant); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the specified tenant from a domain + * @param tenantDomain name of the tenant domain + * @param providerService name of the provider service, + * format: provider-domain-name.provider-service-name, ex: "sports.storage" + * @return Tenancy object or ZMSClientException will be thrown in case of failure + */ + public Tenancy getTenancy(String tenantDomain, String providerService) { + updatePrincipal(); + try { + return client.getTenancy(tenantDomain, providerService); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete the specified tenant from a domain + * @param tenantDomain name of the tenant domain + * @param providerService name of the provider service, + * format: provider-domain-name.provider-service-name, ex: "sports.storage" + * @param auditRef string containing audit specification or ticket number + */ + public void deleteTenancy(String tenantDomain, String providerService, String auditRef) { + updatePrincipal(); + try { + client.deleteTenancy(tenantDomain, providerService, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Create a new resource group for the specified tenant. The service specifies the + * provider. The tenant must have on-boarded already using the putTenancy call. + * @param tenantDomain name of the tenant domain + * @param providerService name of the provider service, + * format: provider-domain-name.provider-service-name, ex: "sports.storage" + * @param resourceGroup name of the resource group + * @param auditRef string containing audit specification or ticket number + * @param resourceGroupData TenancyResourceGroup object with tenant's resource group details + */ + public void putTenancyResourceGroup(String tenantDomain, String providerService, + String resourceGroup, String auditRef, TenancyResourceGroup resourceGroupData) { + updatePrincipal(); + try { + client.putTenancyResourceGroup(tenantDomain, providerService, resourceGroup, auditRef, resourceGroupData); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete the specified resource group for the tenant. + * @param tenantDomain name of the tenant domain + * @param providerService name of the provider service, format: + * provider-domain-name.provider-service-name, ex: "sports.storage" + * @param resourceGroup name of the resource group + * @param auditRef string containing audit specification or ticket number + */ + public void deleteTenancyResourceGroup(String tenantDomain, String providerService, + String resourceGroup, String auditRef) { + updatePrincipal(); + try { + client.deleteTenancyResourceGroup(tenantDomain, providerService, resourceGroup, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Create tenant roles for the specified tenant + * @param providerDomain name of the provider domain + * @param providerServiceName name of the provider service + * @param tenantDomain name of the tenant's domain + * @param auditRef string containing audit specification or ticket number + * @param tenantRoles Tenant roles + */ + public void putTenantRoles(String providerDomain, String providerServiceName, + String tenantDomain, String auditRef, TenantRoles tenantRoles) { + updatePrincipal(); + try { + client.putTenantRoles(providerDomain, providerServiceName, tenantDomain, auditRef, tenantRoles); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Create tenant roles for the specified tenant resource group. + * @param providerDomain name of the provider domain + * @param providerServiceName name of the provider service + * @param tenantDomain name of the tenant's domain + * @param resourceGroup name of the resource group + * @param auditRef string containing audit specification or ticket number + * @param tenantRoles Tenant roles + */ + public void putTenantResourceGroupRoles(String providerDomain, String providerServiceName, String tenantDomain, + String resourceGroup, String auditRef, TenantResourceGroupRoles tenantRoles) { + updatePrincipal(); + try { + client.putTenantResourceGroupRoles(providerDomain, providerServiceName, tenantDomain, + resourceGroup, auditRef, tenantRoles); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of tenant roles defined for a tenant in a domain + * @param providerDomain name of the provider domain + * @param providerServiceName name of the provider service + * @param tenantDomain name of the tenant's domain + * @return list of tenant roles or ZMSClientException will be thrown in case of failure + */ + public TenantRoles getTenantRoles(String providerDomain, String providerServiceName, String tenantDomain) { + updatePrincipal(); + try { + return client.getTenantRoles(providerDomain, providerServiceName, tenantDomain); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of tenant roles defined for a tenant resource group in a domain + * @param providerDomain name of the provider domain + * @param providerServiceName name of the provider service + * @param tenantDomain name of the tenant's domain + * @param resourceGroup name of the resource group + * @return list of tenant roles or ZMSClientException will be thrown in case of failure + */ + public TenantResourceGroupRoles getTenantResourceGroupRoles(String providerDomain, String providerServiceName, + String tenantDomain, String resourceGroup) { + updatePrincipal(); + try { + return client.getTenantResourceGroupRoles(providerDomain, providerServiceName, + tenantDomain, resourceGroup); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete tenant roles for the specified tenant in a domain + * @param providerDomain name of the provider domain + * @param providerServiceName name of the provider service + * @param tenantDomain name of tenant's domain + * @param auditRef string containing audit specification or ticket number + */ + public void deleteTenantRoles(String providerDomain, String providerServiceName, String tenantDomain, + String auditRef) { + updatePrincipal(); + try { + client.deleteTenantRoles(providerDomain, providerServiceName, tenantDomain, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete tenant roles for the specified tenant resource group in a domain + * @param providerDomain name of the provider domain + * @param providerServiceName name of the provider service + * @param tenantDomain name of tenant's domain + * @param resourceGroup name of the resource group + * @param auditRef string containing audit specification or ticket number + */ + public void deleteTenantResourceGroupRoles(String providerDomain, String providerServiceName, String tenantDomain, + String resourceGroup, String auditRef) { + updatePrincipal(); + try { + client.deleteTenantResourceGroupRoles(providerDomain, providerServiceName, tenantDomain, + resourceGroup, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Requests the ZMS to indicate whether or not the specific request for the + * specified resource with authentication details will be granted or not. + * @param action value of the action to be carried out (e.g. "UPDATE", "DELETE") + * @param resource resource YRN. YRN is defined as {ServiceName})?:({LocationName})?:)?{ResourceName}" + * @param trustDomain (optional) if the access checks involves cross domain check only + * check the specified trusted domain and ignore all others + * @return Access object indicating whether or not the request will be granted or not + */ + public Access getAccess(String action, String resource, String trustDomain) { + return getAccess(action, resource, trustDomain, null); + } + + /** + * Requests the ZMS to indicate whether or not the specific request for the + * specified resource with authentication details will be granted or not. + * @param action value of the action to be carried out (e.g. "UPDATE", "DELETE") + * @param resource resource YRN. YRN is defined as {ServiceName})?:({LocationName})?:)?{ResourceName}" + * @param trustDomain (optional) if the access checks involves cross domain check only + * check the specified trusted domain and ignore all others + * @param principal (optional) carry out the access check for specified principal + * @return Access object indicating whether or not the request will be granted or not + */ + public Access getAccess(String action, String resource, String trustDomain, String principal) { + try { + return client.getAccess(action, resource, trustDomain, principal); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + + /** + * Requests the ZMS to indicate whether or not the specific request for the + * specified resource with authentication details will be granted or not. + * @param action value of the action to be carried out (e.g. "UPDATE", "DELETE") + * @param resource resource string. + * @param trustDomain (optional) if the access checks involves cross domain check only + * check the specified trusted domain and ignore all others + * @param principal (optional) carry out the access check for specified principal + * @return Access object indicating whether or not the request will be granted or not + */ + public Access getAccessExt(String action, String resource, String trustDomain, String principal) { + try { + return client.getAccessExt(action, resource, trustDomain, principal); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of all domain data from the ZMS Server that + * is signed with ZMS's private key. It will pass an optional matchingTag + * so that ZMS can skip returning domains if no changes have taken + * place since that tag was issued. + * @param domainName name of the domain. if specified, the server will + * only return this domain in the result set + * @param metaOnly (can be null) must have value of true or false (default). + * if set to true, zms server will only return meta information + * about each domain (description, last modified timestamp, etc) and + * no role/policy/service details will be returned. + * @param matchingTag (can be null) contains modified timestamp received + * with last request. If null, then return all domains. + * @param responseHeaders contains the "tag" returned for modification + * time of the domains, map key = "tag", List should + * contain a single value timestamp String to be used + * with subsequent call as matchingTag to this API + * @return list of domains signed by ZMS Server + */ + public SignedDomains getSignedDomains(String domainName, String metaOnly, String matchingTag, + Map> responseHeaders) { + updatePrincipal(); + try { + SignedDomains sd = client.getSignedDomains(domainName, metaOnly, matchingTag, responseHeaders); + return sd; + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * For the specified user credentials return the corresponding User Token that + * can be used for authenticating other ZMS operations. The client internally + * automatically calls this method and uses the UserToken if the ZMSClient + * object was initialized with a user principal. + * @param userName name of the user. This is only used to verify that it matches + * the user name from the credentials and is optional. The caller can just pass + * the string "_self_" as the userName to bypass this optional check. + * @return ZMS generated User Token + */ + public UserToken getUserToken(String userName) { + return getUserToken(userName, null); + } + + /** + * For the specified user credentials return the corresponding User Token that + * can be used for authenticating other ZMS operations by any of the specified + * authorized services. + * @param userName name of the user + * @param serviceNames comma separated list of authorized service names + * @return ZMS generated User Token + */ + public UserToken getUserToken(String userName, String serviceNames) { + try { + return client.getUserToken(userName, serviceNames); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * For the specified domain in domainName, a list of default administrators + * can be passed to this method and will be added to the domain's admin role + * In addition this method will ensure that the admin role and policy exist and + * are properly set up + * @param domainName - name of the domain to add default administrators to + * @param auditRef - string containing audit specification or ticket number + * @param defaultAdmins - list of names to be added as default administrators + */ + public void putDefaultAdmins(String domainName, String auditRef, DefaultAdmins defaultAdmins) { + updatePrincipal(); + try { + client.putDefaultAdmins(domainName, auditRef, defaultAdmins); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * The client will validate the given serviceToken against the ZMS Server + * and if the token is valid, it will return a Principal object. + * @param serviceToken token to be validated. + * @return Principal object if the token is successfully validated or + * ZMSClientException will be thrown in case of failure + */ + public Principal getPrincipal(String serviceToken) { + + if (serviceToken == null) { + throw new ZMSClientException(401, "Null service token provided"); + } + + PrincipalToken token = null; + try { + token = new PrincipalToken(serviceToken); + } catch (IllegalArgumentException ex) { + throw new ZMSClientException(ZMSClientException.UNAUTHORIZED, "Invalid service token provided: " + ex.getMessage()); + } + + Principal servicePrincipal = null; + try { + servicePrincipal = SimplePrincipal.create(token.getDomain(), token.getName(), + serviceToken, 0, PRINCIPAL_AUTHORITY); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.UNAUTHORIZED, "Invalid service token provided"); + } + + addCredentials(servicePrincipal); + + ServicePrincipal validatedPrincipal = null; + try { + validatedPrincipal = client.getServicePrincipal(); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + + if (validatedPrincipal == null) { + throw new ZMSClientException(ZMSClientException.UNAUTHORIZED, "Invalid service token provided"); + } + + // before returning let's validate that domain, name and + // credentials match to what was passed to + + if (!servicePrincipal.getDomain().equalsIgnoreCase(validatedPrincipal.getDomain())) { + throw new ZMSClientException(ZMSClientException.UNAUTHORIZED, "Validated principal domain name mismatch"); + } + + if (!servicePrincipal.getName().equalsIgnoreCase(validatedPrincipal.getService())) { + throw new ZMSClientException(ZMSClientException.UNAUTHORIZED, "Validated principal service name mismatch"); + } + + return servicePrincipal; + } + + /** + * Create provider roles for the specified tenant resource group in the tenant domain. + * If the principal requesting this operation has been authorized by the provider + * service itself, then the corresponding tenant roles will be created in the provider + * domain as well thus completing the tenancy on-boarding process in one call. + * @param tenantDomain name of the tenant's domain + * @param providerDomain name of the provider domain + * @param providerServiceName name of the provider service + * @param resourceGroup name of the resource group + * @param auditRef string containing audit specification or ticket number + * @param providerRoles Provider roles + */ + public void putProviderResourceGroupRoles(String tenantDomain, String providerDomain, + String providerServiceName, String resourceGroup, String auditRef, + ProviderResourceGroupRoles providerRoles) { + updatePrincipal(); + try { + client.putProviderResourceGroupRoles(tenantDomain, providerDomain, providerServiceName, + resourceGroup, auditRef, providerRoles); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete the provider roles for the specified tenant resource group from the tenant domain. + * If the principal requesting this operation has been authorized by the provider + * service itself, then the corresponding tenant roles will be deleted from the provider + * domain as well thus completing the process in one call. + * @param tenantDomain name of tenant's domain + * @param providerDomain name of the provider domain + * @param providerServiceName name of the provider service + * @param resourceGroup name of the resource group + * @param auditRef string containing audit specification or ticket number + */ + public void deleteProviderResourceGroupRoles(String tenantDomain, String providerDomain, + String providerServiceName, String resourceGroup, String auditRef) { + updatePrincipal(); + try { + client.deleteProviderResourceGroupRoles(tenantDomain, providerDomain, providerServiceName, + resourceGroup, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of provider roles defined for a tenant resource group in a domain + * @param tenantDomain name of the tenant's domain + * @param providerDomain name of the provider domain + * @param providerServiceName name of the provider service + * @param resourceGroup name of the resource group + * @return list of provider roles or ZMSClientException will be thrown in case of failure + */ + public ProviderResourceGroupRoles getProviderResourceGroupRoles(String tenantDomain, + String providerDomain, String providerServiceName, String resourceGroup) { + updatePrincipal(); + try { + return client.getProviderResourceGroupRoles(tenantDomain, providerDomain, providerServiceName, + resourceGroup); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Check the data for the specified domain object + * @param domain name of the domain to be checked + * @return DomainDataCheck object or ZMSClientException will be thrown in case of failure + */ + public DomainDataCheck getDomainDataCheck(String domain) { + updatePrincipal(); + try { + return client.getDomainDataCheck(domain); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the the specified solution template provisioned on the ZMS Server. + * The template object will include the list of roles and policies that will + * be provisioned in the domain when the template is applied. + * @param template name of the solution template to be retrieved + * @return template object or ZMSClientException will be thrown in case of failure + */ + public Template getTemplate(String template) { + updatePrincipal(); + try { + return client.getTemplate(template); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of solution templates provisioned on the ZMS Server + * @return list of template names or ZMSClientException will be thrown in case of failure + */ + public ServerTemplateList getServerTemplateList() { + updatePrincipal(); + try { + return client.getServerTemplateList(); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Provision the specified solution template roles and policies in the domain + * @param domain name of the domain to be updated + * @param auditRef string containing audit specification or ticket number + * @param templates contains list of template names to be provisioned in the domain + */ + public void putDomainTemplate(String domain, String auditRef, DomainTemplate templates) { + updatePrincipal(); + try { + client.putDomainTemplate(domain, auditRef, templates); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Delete the specified solution template roles and policies from the domain + * @param domain name of the domain to be updated + * @param template is the name of the provisioned template to be deleted + * @param auditRef string containing audit specification or ticket number + */ + public void deleteDomainTemplate(String domain, String template, String auditRef) { + updatePrincipal(); + try { + client.deleteDomainTemplate(domain, template, auditRef); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of solution template provisioned for a domain + * @param domain name of the domain + * @return TemplateList object that includes the list of provisioned solution template names + */ + public DomainTemplateList getDomainTemplateList(String domain) { + updatePrincipal(); + try { + return client.getDomainTemplateList(domain); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the list of resources as defined in their respective assertions + * that the given principal has access to through their role membership + * @param principal the principal name (e.g. user.joe). Must have special + * privileges to execute this query without specifying the principal. + * Check with Athenz Service Administrators if you have a use case to + * request all principals from Athenz Service + * @param action optional field specifying what action to filter assertions on + * @return ResourceAccessList object that lists the set of assertions per principal + */ + public ResourceAccessList getResourceAccessList(String principal, String action) { + updatePrincipal(); + try { + return client.getResourceAccessList(principal, action); + } catch (ResourceException ex) { + throw new ZMSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZMSClientException(ZMSClientException.BAD_REQUEST, ex.getMessage()); + } + } +} diff --git a/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClientException.java b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClientException.java new file mode 100644 index 00000000000..331acf66a58 --- /dev/null +++ b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSClientException.java @@ -0,0 +1,25 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +public class ZMSClientException extends ResourceException { + + private static final long serialVersionUID = -8084410672948347342L; + + public ZMSClientException(int code, Object data) { + super(code, data); + } +} diff --git a/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSRDLGeneratedClient.java b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSRDLGeneratedClient.java new file mode 100644 index 00000000000..39d13d70a64 --- /dev/null +++ b/clients/java/zms/src/main/java/com/yahoo/athenz/zms/ZMSRDLGeneratedClient.java @@ -0,0 +1,1478 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zms; + +import com.yahoo.rdl.*; +import javax.ws.rs.client.*; +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import javax.net.ssl.HostnameVerifier; + +public class ZMSRDLGeneratedClient { + Client client; + WebTarget base; + String credsHeader; + String credsToken; + + public ZMSRDLGeneratedClient(String url) { + client = ClientBuilder.newClient(); + base = client.target(url); + } + + public ZMSRDLGeneratedClient(String url, HostnameVerifier hostnameVerifier) { + client = ClientBuilder.newBuilder() + .hostnameVerifier(hostnameVerifier) + .build(); + base = client.target(url); + } + + public void close() { + client.close(); + } + + public ZMSRDLGeneratedClient setProperty(String name, Object value) { + client = client.property(name, value); + return this; + } + + public ZMSRDLGeneratedClient addCredentials(String header, String token) { + credsHeader = header; + credsToken = token; + return this; + } + + public Domain getDomain(String domain) { + WebTarget target = base.path("/domain/{domain}") + .resolveTemplate("domain", domain); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Domain.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public DomainList getDomainList(Integer limit, String skip, String prefix, Integer depth, String account, Integer productId, String roleMember, String roleName, String modifiedSince) { + WebTarget target = base.path("/domain"); + if (limit != null) { + target = target.queryParam("limit", limit); + } + if (skip != null) { + target = target.queryParam("skip", skip); + } + if (prefix != null) { + target = target.queryParam("prefix", prefix); + } + if (depth != null) { + target = target.queryParam("depth", depth); + } + if (account != null) { + target = target.queryParam("account", account); + } + if (productId != null) { + target = target.queryParam("ypmid", productId); + } + if (roleMember != null) { + target = target.queryParam("member", roleMember); + } + if (roleName != null) { + target = target.queryParam("role", roleName); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (modifiedSince != null) { + invocationBuilder = invocationBuilder.header("If-Modified-Since", modifiedSince); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(DomainList.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Domain postTopLevelDomain(String auditRef, TopLevelDomain detail) { + WebTarget target = base.path("/domain"); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.post(javax.ws.rs.client.Entity.entity(detail, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Domain.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Domain postSubDomain(String parent, String auditRef, SubDomain detail) { + WebTarget target = base.path("/subdomain/{parent}") + .resolveTemplate("parent", parent); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.post(javax.ws.rs.client.Entity.entity(detail, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Domain.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Domain postUserDomain(String name, String auditRef, UserDomain detail) { + WebTarget target = base.path("/userdomain/{name}") + .resolveTemplate("name", name); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.post(javax.ws.rs.client.Entity.entity(detail, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Domain.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TopLevelDomain deleteTopLevelDomain(String name, String auditRef) { + WebTarget target = base.path("/domain/{name}") + .resolveTemplate("name", name); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public SubDomain deleteSubDomain(String parent, String name, String auditRef) { + WebTarget target = base.path("/subdomain/{parent}/{name}") + .resolveTemplate("parent", parent) + .resolveTemplate("name", name); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public UserDomain deleteUserDomain(String name, String auditRef) { + WebTarget target = base.path("/userdomain/{name}") + .resolveTemplate("name", name); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Domain putDomainMeta(String name, String auditRef, DomainMeta detail) { + WebTarget target = base.path("/domain/{name}/meta") + .resolveTemplate("name", name); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(detail, "application/json")); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public DomainTemplate putDomainTemplate(String name, String auditRef, DomainTemplate template) { + WebTarget target = base.path("/domain/{name}/template") + .resolveTemplate("name", name); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(template, "application/json")); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public DomainTemplateList getDomainTemplateList(String name) { + WebTarget target = base.path("/domain/{name}/template") + .resolveTemplate("name", name); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(DomainTemplateList.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public DomainTemplate deleteDomainTemplate(String name, String template, String auditRef) { + WebTarget target = base.path("/domain/{name}/template/{template}") + .resolveTemplate("name", name) + .resolveTemplate("template", template); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public DomainDataCheck getDomainDataCheck(String domainName) { + WebTarget target = base.path("/domain/{domainName}/check") + .resolveTemplate("domainName", domainName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(DomainDataCheck.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Entity putEntity(String domainName, String entityName, String auditRef, Entity entity) { + WebTarget target = base.path("/domain/{domainName}/entity/{entityName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("entityName", entityName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(entity, "application/json")); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Entity getEntity(String domainName, String entityName) { + WebTarget target = base.path("/domain/{domainName}/entity/{entityName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("entityName", entityName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Entity.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Entity deleteEntity(String domainName, String entityName, String auditRef) { + WebTarget target = base.path("/domain/{domainName}/entity/{entityName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("entityName", entityName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public EntityList getEntityList(String domainName) { + WebTarget target = base.path("/domain/{domainName}/entity") + .resolveTemplate("domainName", domainName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(EntityList.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public RoleList getRoleList(String domainName, Integer limit, String skip) { + WebTarget target = base.path("/domain/{domainName}/role") + .resolveTemplate("domainName", domainName); + if (limit != null) { + target = target.queryParam("limit", limit); + } + if (skip != null) { + target = target.queryParam("skip", skip); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(RoleList.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Roles getRoles(String domainName, Boolean members) { + WebTarget target = base.path("/domain/{domainName}/roles") + .resolveTemplate("domainName", domainName); + if (members != null) { + target = target.queryParam("members", members); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Roles.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Role getRole(String domainName, String roleName, Boolean auditLog, Boolean expand) { + WebTarget target = base.path("/domain/{domainName}/role/{roleName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("roleName", roleName); + if (auditLog != null) { + target = target.queryParam("auditLog", auditLog); + } + if (expand != null) { + target = target.queryParam("expand", expand); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Role.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Role putRole(String domainName, String roleName, String auditRef, Role role) { + WebTarget target = base.path("/domain/{domainName}/role/{roleName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("roleName", roleName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(role, "application/json")); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Role deleteRole(String domainName, String roleName, String auditRef) { + WebTarget target = base.path("/domain/{domainName}/role/{roleName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("roleName", roleName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Membership getMembership(String domainName, String roleName, String memberName) { + WebTarget target = base.path("/domain/{domainName}/role/{roleName}/member/{memberName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("roleName", roleName) + .resolveTemplate("memberName", memberName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Membership.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Membership putMembership(String domainName, String roleName, String memberName, String auditRef, Membership membership) { + WebTarget target = base.path("/domain/{domainName}/role/{roleName}/member/{memberName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("roleName", roleName) + .resolveTemplate("memberName", memberName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(membership, "application/json")); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Membership deleteMembership(String domainName, String roleName, String memberName, String auditRef) { + WebTarget target = base.path("/domain/{domainName}/role/{roleName}/member/{memberName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("roleName", roleName) + .resolveTemplate("memberName", memberName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public DefaultAdmins putDefaultAdmins(String domainName, String auditRef, DefaultAdmins defaultAdmins) { + WebTarget target = base.path("/domain/{domainName}/admins") + .resolveTemplate("domainName", domainName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(defaultAdmins, "application/json")); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public PolicyList getPolicyList(String domainName, Integer limit, String skip) { + WebTarget target = base.path("/domain/{domainName}/policy") + .resolveTemplate("domainName", domainName); + if (limit != null) { + target = target.queryParam("limit", limit); + } + if (skip != null) { + target = target.queryParam("skip", skip); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(PolicyList.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Policies getPolicies(String domainName, Boolean assertions) { + WebTarget target = base.path("/domain/{domainName}/policies") + .resolveTemplate("domainName", domainName); + if (assertions != null) { + target = target.queryParam("assertions", assertions); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Policies.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Policy getPolicy(String domainName, String policyName) { + WebTarget target = base.path("/domain/{domainName}/policy/{policyName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("policyName", policyName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Policy.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Policy putPolicy(String domainName, String policyName, String auditRef, Policy policy) { + WebTarget target = base.path("/domain/{domainName}/policy/{policyName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("policyName", policyName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(policy, "application/json")); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Policy deletePolicy(String domainName, String policyName, String auditRef) { + WebTarget target = base.path("/domain/{domainName}/policy/{policyName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("policyName", policyName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Assertion getAssertion(String domainName, String policyName, Long assertionId) { + WebTarget target = base.path("/domain/{domainName}/policy/{policyName}/assertion/{assertionId}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("policyName", policyName) + .resolveTemplate("assertionId", assertionId); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Assertion.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Assertion putAssertion(String domainName, String policyName, String auditRef, Assertion assertion) { + WebTarget target = base.path("/domain/{domainName}/policy/{policyName}/assertion") + .resolveTemplate("domainName", domainName) + .resolveTemplate("policyName", policyName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(assertion, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + case 201: + return response.readEntity(Assertion.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Assertion deleteAssertion(String domainName, String policyName, Long assertionId, String auditRef) { + WebTarget target = base.path("/domain/{domainName}/policy/{policyName}/assertion/{assertionId}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("policyName", policyName) + .resolveTemplate("assertionId", assertionId); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ServiceIdentity putServiceIdentity(String domain, String service, String auditRef, ServiceIdentity detail) { + WebTarget target = base.path("/domain/{domain}/service/{service}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(detail, "application/json")); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ServiceIdentity getServiceIdentity(String domain, String service) { + WebTarget target = base.path("/domain/{domain}/service/{service}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(ServiceIdentity.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ServiceIdentity deleteServiceIdentity(String domain, String service, String auditRef) { + WebTarget target = base.path("/domain/{domain}/service/{service}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ServiceIdentities getServiceIdentities(String domainName, Boolean publickeys, Boolean hosts) { + WebTarget target = base.path("/domain/{domainName}/services") + .resolveTemplate("domainName", domainName); + if (publickeys != null) { + target = target.queryParam("publickeys", publickeys); + } + if (hosts != null) { + target = target.queryParam("hosts", hosts); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(ServiceIdentities.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ServiceIdentityList getServiceIdentityList(String domainName, Integer limit, String skip) { + WebTarget target = base.path("/domain/{domainName}/service") + .resolveTemplate("domainName", domainName); + if (limit != null) { + target = target.queryParam("limit", limit); + } + if (skip != null) { + target = target.queryParam("skip", skip); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(ServiceIdentityList.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public PublicKeyEntry getPublicKeyEntry(String domain, String service, String id) { + WebTarget target = base.path("/domain/{domain}/service/{service}/publickey/{id}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service) + .resolveTemplate("id", id); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(PublicKeyEntry.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public PublicKeyEntry putPublicKeyEntry(String domain, String service, String id, String auditRef, PublicKeyEntry publicKeyEntry) { + WebTarget target = base.path("/domain/{domain}/service/{service}/publickey/{id}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service) + .resolveTemplate("id", id); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(publicKeyEntry, "application/json")); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public PublicKeyEntry deletePublicKeyEntry(String domain, String service, String id, String auditRef) { + WebTarget target = base.path("/domain/{domain}/service/{service}/publickey/{id}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service) + .resolveTemplate("id", id); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Tenancy putTenancy(String domain, String service, String auditRef, Tenancy detail) { + WebTarget target = base.path("/domain/{domain}/tenancy/{service}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(detail, "application/json")); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Tenancy getTenancy(String domain, String service) { + WebTarget target = base.path("/domain/{domain}/tenancy/{service}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Tenancy.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Tenancy deleteTenancy(String domain, String service, String auditRef) { + WebTarget target = base.path("/domain/{domain}/tenancy/{service}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TenancyResourceGroup putTenancyResourceGroup(String domain, String service, String resourceGroup, String auditRef, TenancyResourceGroup detail) { + WebTarget target = base.path("/domain/{domain}/tenancy/{service}/resourceGroup/{resourceGroup}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service) + .resolveTemplate("resourceGroup", resourceGroup); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(detail, "application/json")); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TenancyResourceGroup deleteTenancyResourceGroup(String domain, String service, String resourceGroup, String auditRef) { + WebTarget target = base.path("/domain/{domain}/tenancy/{service}/resourceGroup/{resourceGroup}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service) + .resolveTemplate("resourceGroup", resourceGroup); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TenantRoles putTenantRoles(String domain, String service, String tenantDomain, String auditRef, TenantRoles detail) { + WebTarget target = base.path("/domain/{domain}/service/{service}/tenant/{tenantDomain}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service) + .resolveTemplate("tenantDomain", tenantDomain); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(detail, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + case 201: + return response.readEntity(TenantRoles.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TenantRoles getTenantRoles(String domain, String service, String tenantDomain) { + WebTarget target = base.path("/domain/{domain}/service/{service}/tenant/{tenantDomain}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service) + .resolveTemplate("tenantDomain", tenantDomain); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(TenantRoles.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TenantRoles deleteTenantRoles(String domain, String service, String tenantDomain, String auditRef) { + WebTarget target = base.path("/domain/{domain}/service/{service}/tenant/{tenantDomain}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service) + .resolveTemplate("tenantDomain", tenantDomain); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TenantResourceGroupRoles putTenantResourceGroupRoles(String domain, String service, String tenantDomain, String resourceGroup, String auditRef, TenantResourceGroupRoles detail) { + WebTarget target = base.path("/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service) + .resolveTemplate("tenantDomain", tenantDomain) + .resolveTemplate("resourceGroup", resourceGroup); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(detail, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + case 201: + return response.readEntity(TenantResourceGroupRoles.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TenantResourceGroupRoles getTenantResourceGroupRoles(String domain, String service, String tenantDomain, String resourceGroup) { + WebTarget target = base.path("/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service) + .resolveTemplate("tenantDomain", tenantDomain) + .resolveTemplate("resourceGroup", resourceGroup); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(TenantResourceGroupRoles.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TenantResourceGroupRoles deleteTenantResourceGroupRoles(String domain, String service, String tenantDomain, String resourceGroup, String auditRef) { + WebTarget target = base.path("/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service) + .resolveTemplate("tenantDomain", tenantDomain) + .resolveTemplate("resourceGroup", resourceGroup); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ProviderResourceGroupRoles putProviderResourceGroupRoles(String tenantDomain, String provDomain, String provService, String resourceGroup, String auditRef, ProviderResourceGroupRoles detail) { + WebTarget target = base.path("/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + .resolveTemplate("tenantDomain", tenantDomain) + .resolveTemplate("provDomain", provDomain) + .resolveTemplate("provService", provService) + .resolveTemplate("resourceGroup", resourceGroup); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(detail, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + case 201: + return response.readEntity(ProviderResourceGroupRoles.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ProviderResourceGroupRoles getProviderResourceGroupRoles(String tenantDomain, String provDomain, String provService, String resourceGroup) { + WebTarget target = base.path("/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + .resolveTemplate("tenantDomain", tenantDomain) + .resolveTemplate("provDomain", provDomain) + .resolveTemplate("provService", provService) + .resolveTemplate("resourceGroup", resourceGroup); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(ProviderResourceGroupRoles.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ProviderResourceGroupRoles deleteProviderResourceGroupRoles(String tenantDomain, String provDomain, String provService, String resourceGroup, String auditRef) { + WebTarget target = base.path("/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + .resolveTemplate("tenantDomain", tenantDomain) + .resolveTemplate("provDomain", provDomain) + .resolveTemplate("provService", provService) + .resolveTemplate("resourceGroup", resourceGroup); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Access getAccess(String action, String resource, String domain, String checkPrincipal) { + WebTarget target = base.path("/access/{action}/{resource}") + .resolveTemplate("action", action) + .resolveTemplate("resource", resource); + if (domain != null) { + target = target.queryParam("domain", domain); + } + if (checkPrincipal != null) { + target = target.queryParam("principal", checkPrincipal); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Access.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Access getAccessExt(String action, String resource, String domain, String checkPrincipal) { + WebTarget target = base.path("/access/{action}") + .resolveTemplate("action", action); + if (resource != null) { + target = target.queryParam("resource", resource); + } + if (domain != null) { + target = target.queryParam("domain", domain); + } + if (checkPrincipal != null) { + target = target.queryParam("principal", checkPrincipal); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Access.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ResourceAccessList getResourceAccessList(String principal, String action) { + WebTarget target = base.path("/resource"); + if (principal != null) { + target = target.queryParam("principal", principal); + } + if (action != null) { + target = target.queryParam("action", action); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(ResourceAccessList.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public SignedDomains getSignedDomains(String domain, String metaOnly, String matchingTag, java.util.Map> headers) { + WebTarget target = base.path("/sys/modified_domains"); + if (domain != null) { + target = target.queryParam("domain", domain); + } + if (metaOnly != null) { + target = target.queryParam("metaonly", metaOnly); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (matchingTag != null) { + invocationBuilder = invocationBuilder.header("If-None-Match", matchingTag); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + case 304: + if (headers != null) { + headers.put("tag", java.util.Arrays.asList((String)response.getHeaders().getFirst("ETag"))); + } + if (code == 304) { + return null; + } + return response.readEntity(SignedDomains.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public UserToken getUserToken(String userName, String serviceNames) { + WebTarget target = base.path("/user/{userName}/token") + .resolveTemplate("userName", userName); + if (serviceNames != null) { + target = target.queryParam("services", serviceNames); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(UserToken.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public UserToken optionsUserToken(String userName, String serviceNames) { + WebTarget target = base.path("/user/{userName}/token") + .resolveTemplate("userName", userName); + if (serviceNames != null) { + target = target.queryParam("services", serviceNames); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + Response response = invocationBuilder.options(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(UserToken.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ServicePrincipal getServicePrincipal() { + WebTarget target = base.path("/principal"); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(ServicePrincipal.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ServerTemplateList getServerTemplateList() { + WebTarget target = base.path("/template"); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(ServerTemplateList.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Template getTemplate(String template) { + WebTarget target = base.path("/template/{template}") + .resolveTemplate("template", template); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Template.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + +} diff --git a/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.core.JsonFactory b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.core.JsonFactory new file mode 100644 index 00000000000..d1ec3d025c3 --- /dev/null +++ b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.core.JsonFactory @@ -0,0 +1 @@ +athenz.shade.zms.com.fasterxml.jackson.core.JsonFactory diff --git a/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.core.ObjectCodec b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.core.ObjectCodec new file mode 100644 index 00000000000..9d360420066 --- /dev/null +++ b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.core.ObjectCodec @@ -0,0 +1 @@ +athenz.shade.zms.com.fasterxml.jackson.databind.ObjectMapper diff --git a/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.databind.Module b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.databind.Module new file mode 100644 index 00000000000..da39184c36f --- /dev/null +++ b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.com.fasterxml.jackson.databind.Module @@ -0,0 +1 @@ +athenz.shade.zms.com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule diff --git a/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.client.ClientBuilder b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.client.ClientBuilder new file mode 100644 index 00000000000..781eadbf366 --- /dev/null +++ b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.client.ClientBuilder @@ -0,0 +1 @@ +athenz.shade.zms.org.glassfish.jersey.client.JerseyClientBuilder diff --git a/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.MessageBodyReader b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.MessageBodyReader new file mode 100644 index 00000000000..195261405f6 --- /dev/null +++ b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.MessageBodyReader @@ -0,0 +1 @@ +athenz.shade.zms.com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider diff --git a/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.MessageBodyWriter b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.MessageBodyWriter new file mode 100644 index 00000000000..195261405f6 --- /dev/null +++ b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.MessageBodyWriter @@ -0,0 +1 @@ +athenz.shade.zms.com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider diff --git a/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.RuntimeDelegate b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.RuntimeDelegate new file mode 100644 index 00000000000..2cddd883218 --- /dev/null +++ b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.javax.ws.rs.ext.RuntimeDelegate @@ -0,0 +1 @@ +athenz.shade.zms.org.glassfish.jersey.internal.RuntimeDelegateImpl diff --git a/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.org.glassfish.hk2.extension.ServiceLocatorGenerator b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.org.glassfish.hk2.extension.ServiceLocatorGenerator new file mode 100644 index 00000000000..b5ccd1a122f --- /dev/null +++ b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.org.glassfish.hk2.extension.ServiceLocatorGenerator @@ -0,0 +1 @@ +athenz.shade.zms.org.jvnet.hk2.external.generator.ServiceLocatorGeneratorImpl diff --git a/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.org.glassfish.jersey.internal.spi.AutoDiscoverable b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.org.glassfish.jersey.internal.spi.AutoDiscoverable new file mode 100644 index 00000000000..842e2b62138 --- /dev/null +++ b/clients/java/zms/src/main/resources/META-INF/services/athenz.shade.zms.org.glassfish.jersey.internal.spi.AutoDiscoverable @@ -0,0 +1,2 @@ +athenz.shade.zms.org.glassfish.jersey.jackson.internal.JacksonAutoDiscoverable +athenz.shade.zms.org.glassfish.jersey.logging.LoggingFeatureAutoDiscoverable diff --git a/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ResourceExceptionTest.java b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ResourceExceptionTest.java new file mode 100644 index 00000000000..489c3dda0c1 --- /dev/null +++ b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ResourceExceptionTest.java @@ -0,0 +1,88 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +public class ResourceExceptionTest { + public final static int OK = 200; + public final static int CREATED = 201; + public final static int ACCEPTED = 202; + public final static int NO_CONTENT = 204; + public final static int MOVED_PERMANENTLY = 301; + public final static int FOUND = 302; + public final static int SEE_OTHER = 303; + public final static int NOT_MODIFIED = 304; + public final static int TEMPORARY_REDIRECT = 307; + public final static int BAD_REQUEST = 400; + public final static int UNAUTHORIZED = 401; + public final static int FORBIDDEN = 403; + public final static int NOT_FOUND = 404; + public final static int CONFLICT = 409; + public final static int GONE = 410; + public final static int PRECONDITION_FAILED = 412; + public final static int UNSUPPORTED_MEDIA_TYPE = 415; + public final static int INTERNAL_SERVER_ERROR = 500; + public final static int NOT_IMPLEMENTED = 501; + public final static int SERVICE_UNAVAILABLE = 503; + + @Test + public void testCodeToString() { + + assertEquals("OK", ResourceException.codeToString(200)); + assertEquals("Created", ResourceException.codeToString(201)); + assertEquals("Accepted", ResourceException.codeToString(202)); + assertEquals("No Content", ResourceException.codeToString(204)); + assertEquals("Moved Permanently", ResourceException.codeToString(301)); + assertEquals("Found", ResourceException.codeToString(302)); + assertEquals("See Other", ResourceException.codeToString(303)); + assertEquals("Not Modified", ResourceException.codeToString(304)); + assertEquals("Temporary Redirect", ResourceException.codeToString(307)); + assertEquals("Bad Request", ResourceException.codeToString(400)); + assertEquals("Unauthorized", ResourceException.codeToString(401)); + assertEquals("Forbidden", ResourceException.codeToString(403)); + assertEquals("Not Found", ResourceException.codeToString(404)); + assertEquals("Conflict", ResourceException.codeToString(409)); + assertEquals("Gone", ResourceException.codeToString(410)); + assertEquals("Precondition Failed", ResourceException.codeToString(412)); + assertEquals("Unsupported Media Type", ResourceException.codeToString(415)); + assertEquals("Internal Server Error", ResourceException.codeToString(500)); + assertEquals("Not Implemented", ResourceException.codeToString(501)); + assertEquals("1001", ResourceException.codeToString(1001)); + } + + @Test + public void testCodeOnly() { + + ResourceException exc = new ResourceException(400); + assertEquals(exc.getData().toString(), "{code: 400, message: \"Bad Request\"}"); + } + + @Test + public void testGetData() { + + ResourceException exc = new ResourceException(400, "Invalid domain name"); + assertEquals(exc.getData(), "Invalid domain name"); + } + + @Test + public void testGetDataCast() { + + ResourceException exc = new ResourceException(400, new Integer(5000)); + assertEquals(exc.getData(Integer.class), new Integer(5000)); + } +} diff --git a/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSAuthorizerTest.java b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSAuthorizerTest.java new file mode 100644 index 00000000000..a3a327d4675 --- /dev/null +++ b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSAuthorizerTest.java @@ -0,0 +1,349 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.zms.ZMSAuthorizer; + +import static org.testng.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import org.mockito.Mockito; + +public class ZMSAuthorizerTest { + + private static String ZMS_CLIENT_PROP_TEST_ADMIN = "athenz.zms.client.test_admin"; + private static final String AUDIT_REF = "zmsjcltauthtest"; + + private String systemAdminUser = null; + private String systemAdminFullUser = null; + private final String zmsUrl = "http://localhost:10080/"; + + @BeforeClass + public void setup() { + systemAdminUser = System.getProperty(ZMS_CLIENT_PROP_TEST_ADMIN, "user_admin"); + systemAdminFullUser = "user." + systemAdminUser; + System.setProperty(ZMSClient.ZMS_CLIENT_PROP_ATHENZ_CONF, "src/test/resources/athenz.conf"); + } + + @Test + public void testAuthorizer() { + + ZMSClient client = getClient(systemAdminUser); + String domain = "AuthorizerDom1"; + ZMSAuthorizer authorizer = new ZMSAuthorizer(zmsUrl, domain); + assertNotNull(authorizer); + + // create 3 user client objects + + Principal p1 = createPrincipal("user1"); + Principal p2 = createPrincipal("user2"); + Principal p3 = createPrincipal("user3"); + + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class))) + .thenReturn(domainMock); + + setupAccess(client, domain); + + // only user1 and user3 have access to UPDATE/resource1 + ZMSClient mockZMSClient = Mockito.mock(ZMSClient.class); + authorizer.setZMSClient(mockZMSClient); + Access accessMock = Mockito.mock(Access.class); + Mockito.when(mockZMSClient.getAccess("UPDATE", "AuthorizerDom1:resource1", "AuthorizerDom1")) + .thenReturn(accessMock, accessMock, accessMock); + Mockito.when(accessMock.getGranted()).thenReturn(true, true, false, false, true, true); + Mockito.when(c.getAccess("UPDATE", "AuthorizerDom1:resource1", "AuthorizerDom1", null)).thenReturn(accessMock); + + boolean access = authorizer.access("UPDATE", "resource1", p1, domain); + assertTrue(access); + + // we're going to use a principal token as well to test this access + + String principalToken1 = "v=U1;d=user;n=user1;s=signature"; + access = authorizer.access("UPDATE", "resource1", principalToken1, domain); + assertTrue(access); + + access = authorizer.access("UPDATE", "resource1", p2, domain); + assertFalse(access); + + String principalToken2 = "v=U1;d=user;n=user2;s=signature"; + access = authorizer.access("UPDATE", "resource1", principalToken2, domain); + assertFalse(access); + + access = authorizer.access("UPDATE", "resource1", p3, domain); + assertTrue(access); + + String principalToken3 = "v=U1;d=user;n=user3;s=signature"; + access = authorizer.access("UPDATE", "resource1", principalToken3, domain); + assertTrue(access); + + // we should get exception with no principal + try { + authorizer.access("UPDATE", "resource1", (Principal) null, domain); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + // we should get exception with no principal token + + try { + authorizer.access("UPDATE", "resource1", (String) null, domain); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + TopLevelDomain topLevelDomainMock = Mockito.mock(TopLevelDomain.class); + Mockito.when(c.deleteTopLevelDomain(domain, AUDIT_REF)).thenReturn(topLevelDomainMock); + cleanUpAccess(domain); + } + + @Test + public void testAuthorizerNoEndpoint() { + String domain = "AuthorizerDom2"; + ZMSAuthorizer authorizer = new ZMSAuthorizer(domain); + assertNotNull(authorizer); + } + + @Test + public void TestAddCredentials() { + ZMSClient client = getClient(systemAdminUser); + String domain = "AuthorizerDom5"; + ZMSAuthorizer authorizer = new ZMSAuthorizer(zmsUrl, null); + + Principal p1 = createPrincipal("user1"); + Principal p2 = createPrincipal("user2"); + Principal p3 = createPrincipal("user3"); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class))) + .thenReturn(domainMock); + + setupAccess(client, domain); + ZMSClient mockZMSClient = Mockito.mock(ZMSClient.class); + authorizer.setZMSClient(mockZMSClient); + + Access accessMock = Mockito.mock(Access.class); + Mockito.when(mockZMSClient.getAccess("UPDATE", "AuthorizerDom3:resource1", "AuthorizerDom3")) + .thenReturn(accessMock); + Mockito.when(accessMock.getGranted()).thenReturn(true, false, true); + Mockito.when(c.getAccess("UPDATE", "AuthorizerDom3:resource1", "AuthorizerDom3", null)).thenReturn(accessMock); + try { + Mockito.when(mockZMSClient.addCredentials(p1)).thenThrow(new ResourceException(204)); + authorizer.access("UPDATE", domain + ":resource1", p1, domain); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + try { + Mockito.when(mockZMSClient.addCredentials(p2)).thenThrow(new ZMSClientException(204, "No Content")); + authorizer.access("UPDATE", domain + ":resource1", p2, domain); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + + try { + Mockito.when(mockZMSClient.addCredentials(p3)).thenThrow(new ZMSClientException(404, "Not Found")); + authorizer.access("UPDATE", domain + ":resource1", p3, domain); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + authorizer.close(); + } + + @Test + public void TestAuthorizerNoDomain() { + ZMSClient client = getClient(systemAdminUser); + String domain = "AuthorizerDom3"; + ZMSAuthorizer authorizer = new ZMSAuthorizer(zmsUrl, null); + assertNotNull(authorizer); + + // create 3 user client objects + + Principal p1 = createPrincipal("user1"); + Principal p2 = createPrincipal("user2"); + Principal p3 = createPrincipal("user3"); + + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class))) + .thenReturn(domainMock); + + setupAccess(client, domain); + ZMSClient mockZMSClient = Mockito.mock(ZMSClient.class); + authorizer.setZMSClient(mockZMSClient); + + // only user1 and user3 have access to UPDATE/resource1 + Access accessMock = Mockito.mock(Access.class); + Mockito.when(mockZMSClient.getAccess("UPDATE", "AuthorizerDom3:resource1", "AuthorizerDom3")) + .thenReturn(accessMock); + Mockito.when(accessMock.getGranted()).thenReturn(true, false, true); + Mockito.when(c.getAccess("UPDATE", "AuthorizerDom3:resource1", "AuthorizerDom3", null)).thenReturn(accessMock); + boolean access = authorizer.access("UPDATE", domain + ":resource1", p1, domain); + assertTrue(access); + + access = authorizer.access("UPDATE", domain + ":resource1", p2, domain); + assertFalse(access); + + access = authorizer.access("UPDATE", domain + ":resource1", p3, domain); + assertTrue(access); + TopLevelDomain topLevelDomainMock = Mockito.mock(TopLevelDomain.class); + Mockito.when(c.deleteTopLevelDomain(domain, AUDIT_REF)).thenReturn(topLevelDomainMock); + authorizer.close(); + cleanUpAccess(domain); + } + + @Test + public void TestAuthorizerResourceWithDomain() { + ZMSClient client = getClient(systemAdminUser); + String domain = "AuthorizerDom4"; + ZMSAuthorizer authorizer = new ZMSAuthorizer(zmsUrl, domain); + assertNotNull(authorizer); + + // create 3 user client objects + + Principal p1 = createPrincipal("user1"); + Principal p2 = createPrincipal("user2"); + Principal p3 = createPrincipal("user3"); + + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class))) + .thenReturn(domainMock); + setupAccess(client, domain); + + // only user1 and user3 have access to UPDATE/resource1 + ZMSClient mockZMSClient = Mockito.mock(ZMSClient.class); + authorizer.setZMSClient(mockZMSClient); + Access accessMock = Mockito.mock(Access.class); + Mockito.when(mockZMSClient.getAccess("UPDATE", "AuthorizerDom4:resource1", "AuthorizerDom4")) + .thenReturn(accessMock); + Mockito.when(accessMock.getGranted()).thenReturn(true, false, true); + Mockito.when(c.getAccess("UPDATE", "AuthorizerDom4:resource1", "AuthorizerDom4", null)).thenReturn(accessMock); + boolean access = authorizer.access("UPDATE", domain + ":resource1", p1, domain); + assertTrue(access); + + access = authorizer.access("UPDATE", domain + ":resource1", p2, domain); + assertFalse(access); + + access = authorizer.access("UPDATE", domain + ":resource1", p3, domain); + assertTrue(access); + + TopLevelDomain topLevelDomainMock = Mockito.mock(TopLevelDomain.class); + Mockito.when(c.deleteTopLevelDomain(domain, AUDIT_REF)).thenReturn(topLevelDomainMock); + cleanUpAccess(domain); + + } + + private Role createRoleObject(ZMSClient client, String domainName, String roleName, String trust, String member1, + String member2) { + + Role role = new Role(); + role.setName(client.generateRoleName(domainName, roleName)); + role.setTrust(trust); + + List members = new ArrayList(); + members.add(member1); + if (member2 != null) { + members.add(member2); + } + role.setMembers(members); + return role; + } + + private Policy createPolicyObject(ZMSClient client, String domainName, String policyName, String roleName, + String action, String resource, AssertionEffect effect) { + + Policy policy = new Policy(); + policy.setName(client.generatePolicyName(domainName, policyName)); + + Assertion assertion = new Assertion(); + assertion.setAction(action); + assertion.setEffect(effect); + assertion.setResource(resource); + assertion.setRole(client.generateRoleName(domainName, roleName)); + + List assertList = new ArrayList(); + assertList.add(assertion); + + policy.setAssertions(assertList); + return policy; + } + + private TopLevelDomain createTopLevelDomainObject(String name, String description, String org, String admin) { + + TopLevelDomain dom = new TopLevelDomain(); + dom.setName(name); + dom.setDescription(description); + dom.setOrg(org); + dom.setEnabled(true); + dom.setYpmId(2000); + + List admins = new ArrayList(); + admins.add(admin); + dom.setAdminUsers(admins); + return dom; + } + + private Principal createPrincipal(String userName) { + Authority authority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + Principal p = SimplePrincipal.create("user", userName, "v=U1;d=user;n=" + userName + ";s=signature", 0, + authority); + return p; + } + + private ZMSClient getClient(String userName) { + ZMSClient client = new ZMSClient(zmsUrl); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + client.addCredentials(createPrincipal(userName)); + return client; + } + + private void setupAccess(ZMSClient client, String domain) { + TopLevelDomain dom1 = createTopLevelDomainObject(domain, "Test Domain1", "testOrg", systemAdminFullUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + Role role1 = createRoleObject(client, domain, "Role1", null, "user.user1", "user.user3"); + client.putRole(domain, "Role1", AUDIT_REF, role1); + + Policy policy1 = createPolicyObject(client, domain, "Policy1", "Role1", "UPDATE", domain + ":resource1", + AssertionEffect.ALLOW); + client.putPolicy(domain, "Policy1", AUDIT_REF, policy1); + } + + private void cleanUpAccess(String domain) { + ZMSClient client = getClient(systemAdminUser); + client.deleteTopLevelDomain(domain, AUDIT_REF); + } +} diff --git a/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientMockTest.java b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientMockTest.java new file mode 100644 index 00000000000..a67b26484a4 --- /dev/null +++ b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientMockTest.java @@ -0,0 +1,1100 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import java.util.List; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; + +import com.yahoo.athenz.auth.Principal; +import com.yahoo.rdl.Struct; +import com.yahoo.rdl.Timestamp; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; +import org.testng.annotations.BeforeMethod; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.Matchers; +import org.testng.annotations.Test; + +public class ZMSClientMockTest { + + @Mock ZMSRDLGeneratedClient mockZMS; + + ZMSClient zclt; + String zmsUrl = ""; + List userList; + String auditRef = "zmsjcltmktest"; + + static final Struct TABLE_PROVIDER_ROLE_ACTIONS = new Struct() + .with("admin", "*").with("writer", "WRITE").with("reader", "READ"); + + @BeforeMethod + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + Mockito.doReturn(new DomainList()).when(mockZMS).getDomainList(Matchers.isA(Integer.class), + Matchers.isA(String.class), Matchers.isA(String.class), Matchers.isA(Integer.class), + Matchers.isA(String.class), Matchers.isA(Integer.class), Matchers.isA(String.class), + Matchers.isA(String.class), Matchers.isA(String.class)); + userList = new ArrayList(); + userList.add("user.johnny"); + zclt = new ZMSClient(zmsUrl); + zclt.client = mockZMS; + } + + @Test + public void testDomain() throws Exception { + + String domName = "testdom"; + Mockito.doReturn(new Domain()).when(mockZMS).getDomain(domName); + Mockito.doReturn(new DomainList()).when(mockZMS).getDomainList(null, null, null, + null, null, null, null, null, null); + + DomainList domList = zclt.getDomainList(); + assertNotNull(domList); + + Domain dom = zclt.getDomain(domName); + assertNotNull(dom); + + try { + TopLevelDomain tld = new TopLevelDomain().setName(domName).setOrg("testOrg") + .setDescription("test domain").setAdminUsers(userList); + dom = zclt.postTopLevelDomain(auditRef, tld); + + DomainMeta meta = new DomainMeta(); + zclt.putDomainMeta(domName, auditRef, meta); + + zclt.deleteTopLevelDomain(domName, auditRef); + } catch (Exception exc) { + fail(); + } + } + + @Test + public void testDomainListModifiedSince() throws Exception { + + DomainList domList = new DomainList(); + List domains = new ArrayList<>(); + domains.add("dom1"); + domList.setNames(domains); + + Date now = new Date(); + DateFormat df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz"); + String modifiedSince = df.format(now); + Mockito.doReturn(domList).when(mockZMS).getDomainList(null, null, null, + null, null, null, null, null, modifiedSince); + + DomainList domainList = zclt.getDomainList(null, null, null, + null, null, null, now); + assertNotNull(domainList); + assertTrue(domainList.getNames().contains("dom1")); + } + + @Test + public void testSubDomain() throws Exception { + + String parentName = "parentdom"; + String subDomName = "childdom"; + + try { + SubDomain subDom = new SubDomain(); + zclt.postSubDomain(parentName, auditRef, subDom); + zclt.deleteSubDomain(parentName, subDomName, auditRef); + + } catch (Exception exc) { + fail(); + } + } + + @Test + public void testUserDomain() throws Exception { + + String userDomName = "userid"; + + try { + UserDomain userDom = new UserDomain(); + userDom.setName(userDomName); + + zclt.postUserDomain(userDomName, auditRef, userDom); + zclt.deleteUserDomain(userDomName, auditRef); + + } catch (Exception exc) { + fail(); + } + } + + @Test + public void testGetUserToken() throws Exception { + + String uname = "johnny"; + Mockito.doReturn(new UserToken()).when(mockZMS).getUserToken(uname, null); + Mockito.doReturn(new UserToken()).when(mockZMS).getUserToken(uname, "coretech.storage"); + + UserToken utok = zclt.getUserToken(uname); + assertNotNull(utok); + + utok = zclt.getUserToken(uname, "coretech.storage"); + assertNotNull(utok); + + utok = zclt.getUserToken(uname, "sports.hockey"); + assertNull(utok); + } + + @Test + public void testPolicy() throws Exception { + + String domName = "johnnies-place"; + String polName = "chefs"; + Mockito.doReturn(new Policy()).when(mockZMS).getPolicy(domName, polName); + Mockito.doReturn(new PolicyList()).when(mockZMS).getPolicyList(domName, null, null); + + ZMSClient zmsclt2 = new ZMSClient(zmsUrl); + zmsclt2.client = mockZMS; + + PolicyList polList = zmsclt2.getPolicyList(domName); + assertNotNull(polList); + + Policy pol = zmsclt2.getPolicy(domName, polName); + assertNotNull(pol); + + try { + zmsclt2.putPolicy(domName, polName, auditRef, pol); + zmsclt2.deletePolicy(domName, polName, auditRef); + } catch (Exception exc) { + fail(); + } + zmsclt2.close(); + } + + @Test + public void testRole() throws Exception { + + String domName = "johnnies-place"; + String roleName = "manager"; + Mockito.doReturn(new Role()).when(mockZMS).getRole(domName, roleName, false, false); + Mockito.doReturn(new RoleList()).when(mockZMS).getRoleList(domName, null, null); + + RoleList roleList = zclt.getRoleList(domName); + assertNotNull(roleList); + + Role role = zclt.getRole(domName, roleName); + assertNotNull(role); + + try { + zclt.putRole(domName, roleName, auditRef, role); + zclt.deletePolicy(domName, roleName, auditRef); + } catch (Exception exc) { + fail(); + } + } + + @Test + public void testRoleChangeLog() throws Exception { + + String domName = "johnnies-place"; + String roleName = "manager"; + Role retRole = new Role(); + retRole.setName(domName + ":role." + roleName); + + List auditLogList = new ArrayList<>(); + RoleAuditLog logEntry = new RoleAuditLog(); + logEntry.setAction("ADD").setAdmin("user.admin").setMember("user.user1") + .setAuditRef("").setCreated(Timestamp.fromCurrentTime()); + auditLogList.add(logEntry); + + logEntry = new RoleAuditLog(); + logEntry.setAction("DELETE").setAdmin("user.admin").setMember("user.user2") + .setAuditRef("audit-ref").setCreated(Timestamp.fromCurrentTime()); + auditLogList.add(logEntry); + + retRole.setAuditLog(auditLogList); + Mockito.doReturn(retRole).when(mockZMS).getRole(domName, roleName, true, false); + + Role role = zclt.getRole(domName, roleName, true); + assertNotNull(role); + List logList = role.getAuditLog(); + assertNotNull(logList); + assertEquals(logList.size(), 2); + } + + @Test + public void testRoleExpand() throws Exception { + + String domName = "role-expand"; + String roleName = "manager"; + + Role retRoleExpand = new Role(); + retRoleExpand.setName(domName + ":role." + roleName); + retRoleExpand.setTrust("trusted-domain"); + List members = new ArrayList<>(); + members.add("user.user1"); + members.add("coretech.service"); + retRoleExpand.setMembers(members); + + Role retRoleNoExpand = new Role(); + retRoleNoExpand.setName(domName + ":role." + roleName); + retRoleNoExpand.setTrust("trusted-domain"); + + Mockito.doReturn(retRoleExpand).when(mockZMS).getRole(domName, roleName, false, true); + Mockito.doReturn(retRoleNoExpand).when(mockZMS).getRole(domName, roleName, false, false); + + // first request with expand option set + + Role role = zclt.getRole(domName, roleName, false, true); + assertNotNull(role); + assertEquals(role.getTrust(), "trusted-domain"); + assertNotNull(role.getMembers()); + assertEquals(role.getMembers().size(), 2); + assertTrue(role.getMembers().contains("user.user1")); + assertTrue(role.getMembers().contains("coretech.service")); + + // next without expand option + + role = zclt.getRole(domName, roleName, false, false); + assertNotNull(role); + assertEquals(role.getTrust(), "trusted-domain"); + assertNull(role.getMembers()); + } + + @Test + public void testGetRoles() throws Exception { + + String domName = "roles-members"; + + Role trustRole = new Role(); + trustRole.setName(domName + ":role.trust-role"); + trustRole.setTrust("trusted-domain"); + + Role groupRoleWithMembers = new Role(); + groupRoleWithMembers.setName(domName + ":role.group-role"); + List members = new ArrayList<>(); + members.add("user.user1"); + members.add("coretech.service"); + groupRoleWithMembers.setMembers(members); + + Role groupRoleWithoutMembers = new Role(); + groupRoleWithoutMembers.setName(domName + ":role.group-role"); + + List retListWithMembers = new ArrayList<>(); + retListWithMembers.add(groupRoleWithMembers); + retListWithMembers.add(trustRole); + Roles retRolesWithMembers = new Roles().setList(retListWithMembers); + + List retListWithoutMembers = new ArrayList<>(); + retListWithoutMembers.add(groupRoleWithoutMembers); + retListWithoutMembers.add(trustRole); + Roles retRolesWithoutMembers = new Roles().setList(retListWithoutMembers); + + Mockito.doReturn(retRolesWithMembers).when(mockZMS).getRoles(domName, true); + Mockito.doReturn(retRolesWithoutMembers).when(mockZMS).getRoles(domName, false); + + // first request with members option set + + Roles roles = zclt.getRoles(domName, true); + assertNotNull(roles); + assertNotNull(roles.getList()); + + boolean groupRoleCheck = false; + boolean trustRoleCheck = false; + for (Role role : roles.getList()) { + switch (role.getName()) { + case "roles-members:role.trust-role": + assertEquals(role.getTrust(), "trusted-domain"); + assertNull(role.getMembers()); + trustRoleCheck = true; + break; + case "roles-members:role.group-role": + assertNull(role.getTrust()); + assertNotNull(role.getMembers()); + assertEquals(role.getMembers().size(), 2); + assertTrue(role.getMembers().contains("user.user1")); + assertTrue(role.getMembers().contains("coretech.service")); + groupRoleCheck = true; + break; + } + } + assertTrue(groupRoleCheck); + assertTrue(trustRoleCheck); + + // next without members option + + roles = zclt.getRoles(domName, false); + assertNotNull(roles); + assertNotNull(roles.getList()); + + groupRoleCheck = false; + trustRoleCheck = false; + for (Role role : roles.getList()) { + switch (role.getName()) { + case "roles-members:role.trust-role": + assertEquals(role.getTrust(), "trusted-domain"); + assertNull(role.getMembers()); + trustRoleCheck = true; + break; + case "roles-members:role.group-role": + assertNull(role.getTrust()); + assertNull(role.getMembers()); + groupRoleCheck = true; + break; + } + } + assertTrue(groupRoleCheck); + assertTrue(trustRoleCheck); + } + + @Test + public void testPutTenancyResourceGroup() throws Exception { + + String tenantDomain = "tenant"; + String providerService = "coretech.storage"; + String resourceGroup = "hockey"; + TenancyResourceGroup tenantResourceGroup = new TenancyResourceGroup(); + tenantResourceGroup.setDomain(tenantDomain).setService(providerService).setResourceGroup(resourceGroup); + Mockito.doReturn(null) + .when(mockZMS).putTenancyResourceGroup(tenantDomain, providerService, resourceGroup, + auditRef, tenantResourceGroup); + + try { + zclt.putTenancyResourceGroup(tenantDomain, providerService, + resourceGroup, auditRef, tenantResourceGroup); + } catch (Exception exc) { + fail(); + } + } + + @Test + public void testDeleteTenancyResourceGroup() throws Exception { + + String tenantDomain = "tenant"; + String providerService = "coretech.storage"; + String resourceGroup = "hockey"; + Mockito.doReturn(null) + .when(mockZMS).deleteTenancyResourceGroup(tenantDomain, providerService, resourceGroup, auditRef); + + try { + zclt.deleteTenancyResourceGroup(tenantDomain, providerService, + resourceGroup, auditRef); + } catch (Exception exc) { + fail(); + } + } + + @Test + public void testPutTenantResourceGroupRoles() throws Exception { + + String tenantDomain = "tenant"; + String providerDomain = "coretech"; + String providerService = "storage"; + String resourceGroup = "hockey"; + TenantResourceGroupRoles tenantRoles = new TenantResourceGroupRoles(); + tenantRoles.setTenant(tenantDomain).setDomain(providerDomain).setService(providerService) + .setResourceGroup(resourceGroup); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + tenantRoles.setRoles(roleActions); + Mockito.doReturn(null) + .when(mockZMS).putTenantResourceGroupRoles(providerDomain, providerService, tenantDomain, + resourceGroup, auditRef, tenantRoles); + + try { + zclt.putTenantResourceGroupRoles(providerDomain, providerService, + tenantDomain, resourceGroup, auditRef, tenantRoles); + } catch (Exception exc) { + fail(); + } + } + + @Test + public void testDeleteTenantResourceGroupRoles() throws Exception { + + String tenantDomain = "tenant"; + String providerDomain = "coretech"; + String providerService = "storage"; + String resourceGroup = "hockey"; + TenantResourceGroupRoles tenantRoles = new TenantResourceGroupRoles(); + tenantRoles.setTenant(tenantDomain).setDomain(providerDomain).setService(providerService) + .setResourceGroup(resourceGroup); + + Mockito.doReturn(null) + .when(mockZMS).deleteTenantResourceGroupRoles(providerDomain, providerService, tenantDomain, + resourceGroup, auditRef); + + try { + zclt.deleteTenantResourceGroupRoles(providerDomain, providerService, tenantDomain, + resourceGroup, auditRef); + } catch (Exception exc) { + fail(); + } + } + + @Test + public void testGetTenantResourceGroupRoles() throws Exception { + + String tenantDomain = "tenant"; + String providerDomain = "coretech"; + String providerService = "storage"; + String resourceGroup = "hockey"; + TenantResourceGroupRoles tenantRoles = new TenantResourceGroupRoles(); + tenantRoles.setTenant(tenantDomain).setDomain(providerDomain).setService(providerService) + .setResourceGroup(resourceGroup); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + tenantRoles.setRoles(roleActions); + Mockito.doReturn(tenantRoles) + .when(mockZMS).getTenantResourceGroupRoles(providerDomain, + providerService, tenantDomain, resourceGroup); + + TenantResourceGroupRoles retRoles = null; + try { + retRoles = zclt.getTenantResourceGroupRoles(providerDomain, + providerService, tenantDomain, resourceGroup); + } catch (Exception exc) { + fail(); + } + + assertNotNull(retRoles); + assertEquals(tenantDomain, retRoles.getTenant()); + assertEquals(providerDomain, retRoles.getDomain()); + assertEquals(providerService, retRoles.getService()); + assertEquals(resourceGroup, retRoles.getResourceGroup()); + + // try to get unknown resource group that would return null + + try { + retRoles = zclt.getTenantResourceGroupRoles(providerDomain, + providerService, tenantDomain, "baseball"); + } catch (Exception exc) { + fail(); + } + + assertNull(retRoles); + } + + @Test + public void testPutProviderResourceGroupRoles() throws Exception { + + String tenantDomain = "tenant"; + String providerDomain = "coretech"; + String providerService = "storage"; + String resourceGroup = "hockey"; + ProviderResourceGroupRoles provRoles = new ProviderResourceGroupRoles(); + provRoles.setTenant(tenantDomain).setDomain(providerDomain) + .setService(providerService).setResourceGroup(resourceGroup); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + provRoles.setRoles(roleActions); + Mockito.doReturn(null) + .when(mockZMS).putProviderResourceGroupRoles(tenantDomain, providerDomain, providerService, + resourceGroup, auditRef, provRoles); + + try { + zclt.putProviderResourceGroupRoles(tenantDomain, providerDomain, providerService, + resourceGroup, auditRef, provRoles); + } catch (Exception exc) { + fail(); + } + } + + @Test + public void testDeleteProviderResourceGroupRoles() throws Exception { + + String tenantDomain = "tenant"; + String providerDomain = "coretech"; + String providerService = "storage"; + String resourceGroup = "hockey"; + ProviderResourceGroupRoles provRoles = new ProviderResourceGroupRoles(); + provRoles.setTenant(tenantDomain).setDomain(providerDomain) + .setService(providerService).setResourceGroup(resourceGroup); + + Mockito.doReturn(null) + .when(mockZMS).deleteProviderResourceGroupRoles(tenantDomain, providerDomain, providerService, + resourceGroup, auditRef); + + try { + zclt.deleteProviderResourceGroupRoles(tenantDomain, providerDomain, providerService, + resourceGroup, auditRef); + } catch (Exception exc) { + fail(); + } + } + + @Test + public void testGetProviderResourceGroupRoles() throws Exception { + + String tenantDomain = "tenant"; + String providerDomain = "coretech"; + String providerService = "storage"; + String resourceGroup = "hockey"; + ProviderResourceGroupRoles provRoles = new ProviderResourceGroupRoles(); + provRoles.setTenant(tenantDomain).setDomain(providerDomain) + .setService(providerService).setResourceGroup(resourceGroup); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + provRoles.setRoles(roleActions); + Mockito.doReturn(provRoles) + .when(mockZMS).getProviderResourceGroupRoles(tenantDomain, providerDomain, providerService, resourceGroup); + + ProviderResourceGroupRoles retRoles = null; + try { + retRoles = zclt.getProviderResourceGroupRoles(tenantDomain, providerDomain, providerService, resourceGroup); + } catch (Exception exc) { + fail(); + } + + assertNotNull(retRoles); + assertEquals(tenantDomain, retRoles.getTenant()); + assertEquals(providerDomain, retRoles.getDomain()); + assertEquals(providerService, retRoles.getService()); + assertEquals(resourceGroup, retRoles.getResourceGroup()); + + // try to get unknown resource group that would return null + + try { + retRoles = zclt.getProviderResourceGroupRoles(tenantDomain, providerDomain, providerService, "baseball"); + } catch (Exception exc) { + fail(); + } + + assertNull(retRoles); + } + + @Test + public void testGetTemplate() throws Exception { + + Template template = new Template(); + Role role = new Role().setName("role1").setTrust("trust-domain"); + List roleList = new ArrayList<>(); + roleList.add(role); + template.setRoles(roleList); + Policy policy = new Policy().setName("policy1"); + List policyList = new ArrayList<>(); + policyList.add(policy); + template.setPolicies(policyList); + + Mockito.doReturn(template).when(mockZMS).getTemplate("vipng"); + + Template solTemplate = zclt.getTemplate("vipng"); + assertNotNull(solTemplate); + List solRoles = solTemplate.getRoles(); + assertNotNull(solRoles); + assertEquals(1, solRoles.size()); + assertEquals("role1", solRoles.get(0).getName()); + assertEquals("trust-domain", solRoles.get(0).getTrust()); + + List solPolicies = solTemplate.getPolicies(); + assertNotNull(solPolicies); + assertEquals(1, solPolicies.size()); + assertEquals("policy1", solPolicies.get(0).getName()); + } + + @Test + public void testGetServerTemplateList() throws Exception { + ServerTemplateList templateList = new ServerTemplateList(); + List names = new ArrayList<>(); + names.add("vipng"); + names.add("mh2"); + templateList.setTemplateNames(names); + + Mockito.doReturn(templateList).when(mockZMS).getServerTemplateList(); + + ServerTemplateList solTemplateList = zclt.getServerTemplateList(); + assertNotNull(solTemplateList); + assertEquals(2, solTemplateList.getTemplateNames().size()); + assertTrue(solTemplateList.getTemplateNames().contains("mh2")); + assertTrue(solTemplateList.getTemplateNames().contains("vipng")); + } + + @Test + public void testGetDomainTemplateList() throws Exception { + DomainTemplateList templateList = new DomainTemplateList(); + List names = new ArrayList<>(); + names.add("vipng"); + names.add("mh2"); + templateList.setTemplateNames(names); + + Mockito.doReturn(templateList).when(mockZMS).getDomainTemplateList("iaas.athenz"); + + DomainTemplateList domTemplateList = zclt.getDomainTemplateList("iaas.athenz"); + assertNotNull(domTemplateList); + assertEquals(2, domTemplateList.getTemplateNames().size()); + assertTrue(domTemplateList.getTemplateNames().contains("mh2")); + assertTrue(domTemplateList.getTemplateNames().contains("vipng")); + } + + @Test + public void testGetPrincipal() { + + ServicePrincipal svcPrincipal = new ServicePrincipal().setDomain("coretech").setService("storage"); + Mockito.doReturn(svcPrincipal).when(mockZMS).getServicePrincipal(); + + Principal principal = zclt.getPrincipal("v=U1;d=coretech;n=storage;s=signature"); + assertNotNull(principal); + assertTrue(principal.getName().equals("storage")); + assertTrue(principal.getDomain().equals("coretech")); + } + + @Test + public void testDomainListByAccount() throws Exception { + + DomainList domList = new DomainList(); + List domains = new ArrayList<>(); + domains.add("dom1"); + domList.setNames(domains); + + DomainList domEmptyList = new DomainList(); + + Mockito.doReturn(domList).when(mockZMS).getDomainList(null, null, null, null, + "1234", null, null, null, null); + Mockito.doReturn(domEmptyList).when(mockZMS).getDomainList(null, null, null, null, + "1235", null, null, null, null); + + DomainList domainList = zclt.getDomainList(null, null, null, null, "1234", null, null); + assertNotNull(domainList); + assertTrue(domainList.getNames().contains("dom1")); + + domainList = zclt.getDomainList(null, null, null, null, "1235", null, null); + assertNotNull(domainList); + assertNull(domainList.getNames()); + } + + @Test + public void testDomainListByProductId() throws Exception { + + DomainList domList = new DomainList(); + List domains = new ArrayList<>(); + domains.add("dom1"); + domList.setNames(domains); + + DomainList domEmptyList = new DomainList(); + + Mockito.doReturn(domList).when(mockZMS).getDomainList(null, null, null, null, null, + Integer.valueOf(101), null, null, null); + Mockito.doReturn(domEmptyList).when(mockZMS).getDomainList(null, null, null, null, null, + Integer.valueOf(102), null, null, null); + + DomainList domainList = zclt.getDomainList(null, null, null, null, null, Integer.valueOf(101), null); + assertNotNull(domainList); + assertTrue(domainList.getNames().contains("dom1")); + + domainList = zclt.getDomainList(null, null, null, null, null, Integer.valueOf(102), null); + assertNotNull(domainList); + assertNull(domainList.getNames()); + } + + @Test + public void testDomainListByRole() throws Exception { + + DomainList domList = new DomainList(); + List domains = new ArrayList<>(); + domains.add("dom1"); + domList.setNames(domains); + + DomainList domEmptyList = new DomainList(); + + Mockito.doReturn(domList).when(mockZMS).getDomainList(null, null, null, null, null, + null, "user.user1", "admin", null); + Mockito.doReturn(domEmptyList).when(mockZMS).getDomainList(null, null, null, null, null, + null, "user.user2", "admin", null); + + DomainList domainList = zclt.getDomainList("user.user1", "admin"); + assertNotNull(domainList); + assertTrue(domainList.getNames().contains("dom1")); + + domainList = zclt.getDomainList("user.user2", "admin"); + assertNotNull(domainList); + assertNull(domainList.getNames()); + } + + @Test + public void testEntityList() { + + String domName = "johnnies-place"; + List names = new ArrayList<>(); + names.add("entity1"); + names.add("entity2"); + EntityList entList = new EntityList().setNames(names); + + Mockito.doReturn(entList).when(mockZMS).getEntityList(domName); + + EntityList list = zclt.getEntityList(domName); + assertNotNull(list); + List ents = list.getNames(); + assertEquals(ents.size(), 2); + assertTrue(ents.contains("entity1")); + assertTrue(ents.contains("entity2")); + } + + @Test + public void testGetResourceAccessList() throws Exception { + + ResourceAccessList rsrcEmptyList = new ResourceAccessList(); + + ResourceAccessList rsrcList = new ResourceAccessList(); + + ResourceAccess rsrcAccess = new ResourceAccess(); + rsrcAccess.setPrincipal("user.user"); + Assertion assertion = new Assertion().setAction("update").setRole("athenz:role.role1").setResource("athenz:resource1"); + List assertions = new ArrayList<>(); + assertions.add(assertion); + rsrcAccess.setAssertions(assertions); + + List resources = new ArrayList<>(); + resources.add(rsrcAccess); + + rsrcList.setResources(resources); + + Mockito.doReturn(rsrcList).when(mockZMS).getResourceAccessList("user.user", "update"); + Mockito.doReturn(rsrcEmptyList).when(mockZMS).getResourceAccessList("user.user1", "create"); + + ResourceAccessList rsrcAccessList = zclt.getResourceAccessList("user.user", "update"); + assertNotNull(rsrcAccessList); + assertEquals(rsrcAccessList.getResources().size(), 1); + + rsrcAccess = rsrcAccessList.getResources().get(0); + assertEquals(rsrcAccess.getPrincipal(), "user.user"); + + rsrcAccessList = zclt.getResourceAccessList("user.user1", "create"); + assertNotNull(rsrcAccessList); + assertNull(rsrcAccessList.getResources()); + } + + @Test + public void testGetPolicies() throws Exception { + + final String domName = "get-policies"; + + Policy policyWithAssertions = new Policy(); + policyWithAssertions.setName(domName + ":policy.assert-policy"); + policyWithAssertions.setModified(Timestamp.fromCurrentTime()); + List assertions = new ArrayList<>(); + Assertion assertion = new Assertion() + .setAction("update") + .setEffect(AssertionEffect.ALLOW) + .setId((long) 101) + .setResource(domName + ":*") + .setRole("admin"); + assertions.add(assertion); + policyWithAssertions.setAssertions(assertions); + + Policy policyWithOutAssertions = new Policy(); + policyWithOutAssertions.setName(domName + ":policy.no-assert-policy"); + policyWithOutAssertions.setModified(Timestamp.fromCurrentTime()); + + List retListWithAssertions = new ArrayList<>(); + retListWithAssertions.add(policyWithAssertions); + Policies retPoliciesWithAssertions = new Policies().setList(retListWithAssertions); + + List retListWithOutAssertions = new ArrayList<>(); + retListWithOutAssertions.add(policyWithOutAssertions); + Policies retPoliciesWithOutAssertions = new Policies().setList(retListWithOutAssertions); + + Mockito.doReturn(retPoliciesWithAssertions).when(mockZMS).getPolicies(domName, true); + Mockito.doReturn(retPoliciesWithOutAssertions).when(mockZMS).getPolicies(domName, false); + + // first request with assertions option set + + Policies policies = zclt.getPolicies(domName, true); + assertNotNull(policies); + assertNotNull(policies.getList()); + + boolean policyCheck = false; + for (Policy policy : policies.getList()) { + switch (policy.getName()) { + case "get-policies:policy.assert-policy": + assertNotNull(policy.getModified()); + List testAssertions = policy.getAssertions(); + assertNotNull(testAssertions); + assertEquals(testAssertions.size(), 1); + assertEquals(testAssertions.get(0).getAction(), "update"); + policyCheck = true; + break; + } + } + assertTrue(policyCheck); + + // next without assertions option + + policies = zclt.getPolicies(domName, false); + assertNotNull(policies); + assertNotNull(policies.getList()); + + policyCheck = false; + for (Policy policy : policies.getList()) { + switch (policy.getName()) { + case "get-policies:policy.no-assert-policy": + assertNotNull(policy.getModified()); + assertNull(policy.getAssertions()); + policyCheck = true; + break; + } + } + assertTrue(policyCheck); + } + + @Test + public void testGetServices() throws Exception { + + final String domName = "get-services"; + + ServiceIdentity serviceWithKeysHosts = new ServiceIdentity(); + serviceWithKeysHosts.setName(domName + ".service-key-host") + .setGroup("users") + .setExecutable("/usr/bin/jetty") + .setModified(Timestamp.fromCurrentTime()) + .setUser("root"); + List publicKeys = new ArrayList<>(); + PublicKeyEntry publicKey = new PublicKeyEntry().setId("0").setKey("key"); + publicKeys.add(publicKey); + serviceWithKeysHosts.setPublicKeys(publicKeys); + + List hosts = new ArrayList<>(); + hosts.add("host1"); + serviceWithKeysHosts.setHosts(hosts); + + ServiceIdentity serviceWithKeysOnly = new ServiceIdentity(); + serviceWithKeysOnly.setName(domName + ".service-key-only") + .setGroup("users") + .setExecutable("/usr/bin/jetty") + .setModified(Timestamp.fromCurrentTime()) + .setUser("root") + .setPublicKeys(publicKeys); + + ServiceIdentity serviceWithHostsOnly = new ServiceIdentity(); + serviceWithHostsOnly.setName(domName + ".service-host-only") + .setGroup("users") + .setExecutable("/usr/bin/jetty") + .setModified(Timestamp.fromCurrentTime()) + .setUser("root") + .setHosts(hosts); + + List retListWithKeysHosts = new ArrayList<>(); + retListWithKeysHosts.add(serviceWithKeysHosts); + ServiceIdentities retServicesWithKeysHosts = new ServiceIdentities().setList(retListWithKeysHosts); + + List retListWithKeysOnly = new ArrayList<>(); + retListWithKeysOnly.add(serviceWithKeysOnly); + ServiceIdentities retServicesWithKeysOnly = new ServiceIdentities().setList(retListWithKeysOnly); + + List retListWithHostsOnly = new ArrayList<>(); + retListWithHostsOnly.add(serviceWithHostsOnly); + ServiceIdentities retServicesWithHostsOnly = new ServiceIdentities().setList(retListWithHostsOnly); + + Mockito.doReturn(retServicesWithKeysHosts).when(mockZMS).getServiceIdentities(domName, true, true); + Mockito.doReturn(retServicesWithKeysOnly).when(mockZMS).getServiceIdentities(domName, true, false); + Mockito.doReturn(retServicesWithHostsOnly).when(mockZMS).getServiceIdentities(domName, false, true); + + // first request with keys and hosts option set + + ServiceIdentities services = zclt.getServiceIdentities(domName, true, true); + assertNotNull(services); + assertNotNull(services.getList()); + + boolean serviceCheck = false; + for (ServiceIdentity service : services.getList()) { + switch (service.getName()) { + case "get-services.service-key-host": + assertNotNull(service.getModified()); + List testPublicKeys = service.getPublicKeys(); + assertNotNull(testPublicKeys); + assertEquals(testPublicKeys.size(), 1); + assertEquals(testPublicKeys.get(0).getId(), "0"); + assertEquals(testPublicKeys.get(0).getKey(), "key"); + List testHosts = service.getHosts(); + assertNotNull(testHosts); + assertEquals(testHosts.size(), 1); + assertEquals(testHosts.get(0), "host1"); + serviceCheck = true; + break; + } + } + assertTrue(serviceCheck); + + // next with only key option + + services = zclt.getServiceIdentities(domName, true, false); + assertNotNull(services); + assertNotNull(services.getList()); + + serviceCheck = false; + for (ServiceIdentity service : services.getList()) { + switch (service.getName()) { + case "get-services.service-key-only": + assertNotNull(service.getModified()); + List testPublicKeys = service.getPublicKeys(); + assertNotNull(testPublicKeys); + assertEquals(testPublicKeys.size(), 1); + assertEquals(testPublicKeys.get(0).getId(), "0"); + assertEquals(testPublicKeys.get(0).getKey(), "key"); + assertNull(service.getHosts()); + serviceCheck = true; + break; + } + } + assertTrue(serviceCheck); + + // next with only host option + + services = zclt.getServiceIdentities(domName, false, true); + assertNotNull(services); + assertNotNull(services.getList()); + + serviceCheck = false; + for (ServiceIdentity service : services.getList()) { + switch (service.getName()) { + case "get-services.service-host-only": + assertNotNull(service.getModified()); + assertNull(service.getPublicKeys()); + List testHosts = service.getHosts(); + assertNotNull(testHosts); + assertEquals(testHosts.size(), 1); + assertEquals(testHosts.get(0), "host1"); + serviceCheck = true; + break; + } + } + assertTrue(serviceCheck); + } + + @Test + public void testGetAssertion() throws Exception { + + final String domName = "get-assertion"; + Assertion assertion = new Assertion() + .setAction("update") + .setEffect(AssertionEffect.ALLOW) + .setId((long) 101) + .setResource(domName + ":*") + .setRole("admin"); + + Mockito.doReturn(assertion).when(mockZMS).getAssertion(domName, "policy1", new Long(101)); + Mockito.doThrow(new ResourceException(404)).when(mockZMS).getAssertion(domName, "policy1", new Long(202)); + + // first invalid test cases + + // unknown policy name + Assertion testAssertion = zclt.getAssertion(domName, "policy2", new Long(101)); + assertNull(testAssertion); + + // unknown domain name + + testAssertion = zclt.getAssertion("unknown", "policy1", new Long(101)); + assertNull(testAssertion); + + // unknown assertion id + + testAssertion = zclt.getAssertion(domName, "policy1", new Long(102)); + assertNull(testAssertion); + + // exception unit test + + try { + zclt.getAssertion(domName, "policy1", new Long(202)); + fail(); + } catch (ZMSClientException ex) { + assertEquals(ex.getCode(), 404); + } + + // now valid case + + testAssertion = zclt.getAssertion(domName, "policy1", new Long(101)); + assertNotNull(testAssertion); + assertEquals(assertion.getAction(), "update"); + assertEquals((long) assertion.getId(), (long) 101); + } + + @Test + public void testDeleteAssertion() throws Exception { + + final String domName = "delete-assertion"; + + Mockito.doReturn(null).when(mockZMS).deleteAssertion(domName, "policy1", new Long(101), auditRef); + Mockito.doThrow(new ResourceException(403)).when(mockZMS).deleteAssertion(domName, "policy1", new Long(202), auditRef); + + // first valid case should complete successfully + + zclt.deleteAssertion(domName, "policy1", new Long(101), auditRef); + + // now this should throw an exception + + try { + zclt.deleteAssertion(domName, "policy1", new Long(202), auditRef); + fail(); + } catch (ZMSClientException ex) { + assertEquals(ex.getCode(), 403); + } + } + + @Test + public void testPutAssertion() throws Exception { + + final String domName = "put-assertion"; + Assertion assertion = new Assertion() + .setAction("update") + .setEffect(AssertionEffect.ALLOW) + .setResource(domName + ":*") + .setRole("admin"); + + Assertion retAssertion = new Assertion() + .setAction("update") + .setEffect(AssertionEffect.ALLOW) + .setId((long) 101) + .setResource(domName + ":*") + .setRole("admin"); + + Mockito.doReturn(retAssertion).when(mockZMS).putAssertion(domName, "policy1", auditRef, assertion); + Mockito.doThrow(new ResourceException(403)).when(mockZMS).putAssertion(domName, "policy2", auditRef, assertion); + + // first valid case should complete successfully + + Assertion checkAssertion = zclt.putAssertion(domName, "policy1", auditRef, assertion); + assertEquals(checkAssertion.getId(), Long.valueOf(101)); + + // now this should throw an exception + + try { + zclt.putAssertion(domName, "policy2", auditRef, assertion); + fail(); + } catch (ZMSClientException ex) { + assertEquals(ex.getCode(), 403); + } + } +} + diff --git a/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientTest.java b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientTest.java new file mode 100644 index 00000000000..338fa823211 --- /dev/null +++ b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSClientTest.java @@ -0,0 +1,2076 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.rdl.Array; +import com.yahoo.rdl.Struct; + +import static org.testng.Assert.*; + +import org.mockito.Mockito; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class ZMSClientTest { + + private String systemAdminUser = null; + private String systemAdminFullUser = null; + private static String ZMS_CLIENT_PROP_ZMS_URL = "athenz.zms.client.zms_url"; + private static String ZMS_CLIENT_PROP_TEST_ADMIN = "athenz.zms.client.test_admin"; + + private static final String PUB_KEY_ZONE1 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BM" + + "EdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FDMXRHU1ZDQTh3bDVldzVZNzZXajJySkFVRA" + + "pZYW5FSmZLbUFseDVjUS84aEtFVWZTU2dwWHIzQ3pkaDFhMjZkbGI3bW1LMjlxbVhKWGg2dW1XOUF" + + "5ZlRPS1ZvCis2QVNsb1ZVM2F2dnVmbEdVT0VnMmpzbWRha1IyNEtjTGpBdTZRclVlNDE3bEczdDhx" + + "U1BJR2pTNUMrQ3NKVXcKaDA0aEh4NWYrUEV3eFY0cmJRSURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLR" + + "VktLS0tLQo-"; + private static final String PUB_KEY_ZONE2 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRW" + + "UpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTDRnNlF1bGVRcG42bytpSmorK09nenNZM3hXekhHUw" + + "p4ZW1xZzZhdkkvbHhvT3Jzd2h4YW93MjMrR3AxZXhOWEdzQlNsTkFQSXh5N3RHTXZaRnY0Q3ZrQ0F" + + "3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + + private boolean printURL = true; + + static final Struct TABLE_PROVIDER_ROLE_ACTIONS = new Struct() + .with("admin", "*").with("writer", "WRITE").with("reader", "READ"); + + private static String AUDIT_REF = "zmsjcltest"; + + static final int BASE_PRODUCT_ID = 100000000; // these product id's will lie in 100 million range + static java.util.Random domainProductId = new java.security.SecureRandom(); + + static synchronized int getRandomProductId() { + return BASE_PRODUCT_ID + domainProductId.nextInt(99999999); + } + + @BeforeClass + public void setup() { + System.setProperty(ZMS_CLIENT_PROP_ZMS_URL, "http://localhost:10080/"); + + systemAdminUser = System.getProperty(ZMS_CLIENT_PROP_TEST_ADMIN, "user_admin"); + systemAdminFullUser = "user." + systemAdminUser; + } + + private Principal createPrincipal(String userName) { + Authority authority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + Principal p = SimplePrincipal.create("user", userName, + "v=U1;d=user;n=" + userName + ";s=signature", 0, authority); + return p; + } + + private DomainMeta createDomainMetaObject(String description, String org, boolean auditEnabled) { + + DomainMeta meta = new DomainMeta(); + meta.setDescription(description); + meta.setOrg(org); + meta.setAuditEnabled(auditEnabled); + + return meta; + } + + private ZMSClient createClient(String userName) { + ZMSClient client = new ZMSClient(getZMSUrl()); + client.addCredentials(createPrincipal(userName)); + + if (printURL) { + System.out.println("ZMS Url set to: " + client.getZmsUrl()); + printURL = false; + } + + return client; + } + + private String getZMSUrl() { + + // if we're given a config setting then use that + + String zmsUrl = System.getProperty(ZMS_CLIENT_PROP_ZMS_URL); + + // if the value is not available then check the env setting + + if (zmsUrl == null) { + zmsUrl = System.getenv("ZMS_URL"); + } + + return zmsUrl; + } + + private TopLevelDomain createTopLevelDomainObject(String name, + String description, String org, String admin) { + + TopLevelDomain dom = new TopLevelDomain(); + dom.setName(name); + dom.setDescription(description); + dom.setOrg(org); + dom.setEnabled(true); + dom.setYpmId(getRandomProductId()); + + List admins = new ArrayList(); + admins.add(admin); + dom.setAdminUsers(admins); + + return dom; + } + + private SubDomain createSubDomainObject(String name, String parent, + String description, String org, String admin) { + + SubDomain dom = new SubDomain(); + dom.setName(name); + dom.setDescription(description); + dom.setOrg(org); + dom.setParent(parent); + dom.setEnabled(true); + + List admins = new ArrayList(); + admins.add(admin); + dom.setAdminUsers(admins); + + return dom; + } + + private Role createRoleObject(ZMSClient client, String domainName, String roleName, + String trust, String member1, String member2) { + + Role role = new Role(); + role.setName(client.generateRoleName(domainName, roleName)); + role.setTrust(trust); + + List members = new ArrayList(); + members.add(member1); + if (member2 != null) { + members.add(member2); + } + role.setMembers(members); + return role; + } + + private Policy createPolicyObject(ZMSClient client, String domainName, String policyName, + String roleName, String action, String resource, AssertionEffect effect) { + + Policy policy = new Policy(); + policy.setName(client.generatePolicyName(domainName, policyName)); + + Assertion assertion = new Assertion(); + assertion.setAction(action); + assertion.setEffect(effect); + assertion.setResource(resource); + assertion.setRole(client.generateRoleName(domainName, roleName)); + + List assertList = new ArrayList(); + assertList.add(assertion); + + policy.setAssertions(assertList); + return policy; + } + + private Policy createPolicyObject(ZMSClient client, String domainName, String policyName) { + return createPolicyObject(client, domainName, policyName, "Role1", "*", domainName + ":*", AssertionEffect.ALLOW); + } + + private ServiceIdentity createServiceObject(ZMSClient client, String domainName, + String serviceName, String endPoint, String executable, String user, + String group, String host) { + + ServiceIdentity service = new ServiceIdentity(); + service.setExecutable(executable); + service.setName(client.generateServiceIdentityName(domainName, serviceName)); + + List pubKeys = new ArrayList<>(); + pubKeys.add(new PublicKeyEntry().setId("0") + .setKey("LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTk" + + "FEQ0JpUUtCZ1FDMXRHU1ZDQTh3bDVldzVZNzZXajJySkFVRApZYW5FSmZLbUFseDVjUS84aEtFVWZTU2dwWHI" + + "zQ3pkaDFhMjZkbGI3bW1LMjlxbVhKWGg2dW1XOUF5ZlRPS1ZvCis2QVNsb1ZVM2F2dnVmbEdVT0VnMmpzbWRh" + + "a1IyNEtjTGpBdTZRclVlNDE3bEczdDhxU1BJR2pTNUMrQ3NKVXcKaDA0aEh4NWYrUEV3eFY0cmJRSURBUUFCC" + + "i0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-")); + + service.setPublicKeys(pubKeys); + + service.setUser(user); + service.setGroup(group); + + service.setProviderEndpoint(endPoint); + + List hosts = new ArrayList(); + hosts.add(host); + service.setHosts(hosts); + + return service; + } + + private ServiceIdentity createServiceObjectWithZoneKeys(ZMSClient client, String domainName, + String serviceName, String endPoint, String executable, String user, + String group, String host) { + + ServiceIdentity service = new ServiceIdentity(); + service.setExecutable(executable); + service.setGroup(group); + service.setName(client.generateServiceIdentityName(domainName, serviceName)); + service.setUser(user); + + List pubKeys = new ArrayList<>(); + + PublicKeyEntry key1 = new PublicKeyEntry(); + key1.setId("zone1"); + key1.setKey(PUB_KEY_ZONE1); + + PublicKeyEntry key2 = new PublicKeyEntry(); + key2.setId("zone2"); + key2.setKey(PUB_KEY_ZONE2); + + pubKeys.add(key1); + pubKeys.add(key2); + service.setPublicKeys(pubKeys); + + service.setProviderEndpoint(endPoint); + + List hosts = new ArrayList(); + hosts.add(host); + service.setHosts(hosts); + + return service; + } + + private Entity createEntityObject(ZMSClient client, String entityName) { + + Entity entity = new Entity(); + entity.setName(entityName); + + Struct value = new Struct(); + value.put("Key1", "Value1"); + entity.setValue(value); + + return entity; + } + + private Tenancy createTenantObject(String domain, String service) { + + Tenancy tenant = new Tenancy(); + tenant.setDomain(domain); + tenant.setService(service); + + return tenant; + } + + private void testCreateTopLevelDomain(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("AddTopDom1", + "Test Domain1", "testOrg", adminUser); + Domain resDom1 = client.postTopLevelDomain(AUDIT_REF, dom1); + assertNotNull(resDom1); + + Domain resDom2 = client.getDomain("AddTopDom1"); + assertNotNull(resDom2); + + try { + client.getDomain("AddTopDom3"); + fail(); + } catch(ResourceException ex) { + assertTrue(true); + } + + try { + client.getDomain("AddTopDom3"); + fail(); + } catch(ResourceException ex) { + assertTrue(true); + } + + client.deleteTopLevelDomain("AddTopDom1", AUDIT_REF); + } + + private void testCreateTopLevelDomainOnceOnly(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("AddOnceTopDom1", + "Test Domain1", "testOrg", adminUser); + Domain resDom1 = client.postTopLevelDomain(AUDIT_REF, dom1); + assertNotNull(resDom1); + + // we should get an exception for the second call + + try { + client.postTopLevelDomain(AUDIT_REF, dom1); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + client.deleteTopLevelDomain("AddOnceTopDom1", AUDIT_REF); + } + + private void testCreateSubDomain(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("AddSubDom1", + "Test Domain1", "testOrg", adminUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + SubDomain dom2 = createSubDomainObject("AddSubDom2", "AddSubDom1", + "Test Domain2", "testOrg", adminUser); + Domain resDom1 = client.postSubDomain("AddSubDom1", AUDIT_REF, dom2); + assertNotNull(resDom1); + try { + client.postSubDomain("AddSubDom3", AUDIT_REF, dom2); + fail(); + } catch(ResourceException ex) { + assertTrue(true); + } + + Domain resDom2 = client.getDomain("AddSubDom1.AddSubDom2"); + assertNotNull(resDom2); + + client.deleteSubDomain("AddSubDom1", "AddSubDom2", AUDIT_REF); + + client.deleteTopLevelDomain("AddSubDom1", AUDIT_REF); + } + + private void testCreateSubdomainOnceOnly(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("AddOnceSubDom1", + "Test Domain1", "testOrg", adminUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + SubDomain dom2 = createSubDomainObject("AddOnceSubDom2", "AddOnceSubDom1", + "Test Domain2", "testOrg", adminUser); + Domain resDom1 = client.postSubDomain("AddOnceSubDom1", AUDIT_REF, dom2); + assertNotNull(resDom1); + + // we should get an exception for the second call + + try { + client.postSubDomain("AddOnceSubDom1", AUDIT_REF, dom2); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + client.deleteSubDomain("AddOnceSubDom1", "AddOnceSubDom2", AUDIT_REF); + client.deleteTopLevelDomain("AddOnceSubDom1", AUDIT_REF); + } + + private void testCreateRole(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("CreateRoleDom1", + "Test Domain1", "testOrg", adminUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + Role role1 = createRoleObject(client, "CreateRoleDom1", "Role1", null, "user.joe", "user.jane"); + client.putRole("CreateRoleDom1", "Role1", AUDIT_REF, role1); + + Role role3 = client.getRole("CreateRoleDom1", "Role1"); + assertNotNull(role3); + assertEquals(role3.getName(), "CreateRoleDom1:role.Role1".toLowerCase()); + assertNull(role3.getTrust()); + + try { + client.putRole("CreateRoleDom1", "Role2", AUDIT_REF, role1); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + client.putRole("CreateRoleDom1", "Role3", AUDIT_REF, role1); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + + try { + client.getRole("CreateRoleDom1", "Role2"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + client.getRole("CreateRoleDom1", "Role3"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + + client.deleteTopLevelDomain("CreateRoleDom1", AUDIT_REF); + } + + private void testAddMembership(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrAddDom1", + "Test Domain1", "testOrg", adminUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + Role role1 = createRoleObject(client, "MbrAddDom1", "Role1", null, "user.joe", "user.jane"); + client.putRole("MbrAddDom1", "Role1", AUDIT_REF, role1); + client.putMembership("MbrAddDom1", "Role1", "user.doe", AUDIT_REF); + Role role = client.getRole("MbrAddDom1", "Role1"); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 3); + + assertTrue(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + assertTrue(members.contains("user.doe")); + + client.deleteTopLevelDomain("MbrAddDom1", AUDIT_REF); + } + + private void testDeleteMembership(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrDelDom1", + "Test Domain1", "testOrg", adminUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + Role role1 = createRoleObject(client, "MbrDelDom1", "Role1", null, "user.joe", "user.jane"); + client.putRole("MbrDelDom1", "Role1", AUDIT_REF, role1); + client.deleteMembership("MbrDelDom1", "Role1", "user.joe", AUDIT_REF); + + Role role = client.getRole("MbrDelDom1", "Role1"); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 1); + + assertFalse(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + + client.deleteTopLevelDomain("MbrDelDom1", AUDIT_REF); + } + + private void testCreatePolicy(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("PolicyAddDom1", + "Test Domain1", "testOrg", adminUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + Policy policy1 = createPolicyObject(client, "PolicyAddDom1", "Policy1"); + client.putPolicy("PolicyAddDom1", "Policy1", AUDIT_REF, policy1); + + Policy policyRes2 = client.getPolicy("PolicyAddDom1", "Policy1"); + assertNotNull(policyRes2); + assertEquals(policyRes2.getName(), "PolicyAddDom1:policy.Policy1".toLowerCase()); + + client.deleteTopLevelDomain("PolicyAddDom1", AUDIT_REF); + } + + private void testDeletePolicy(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("PolicyDelDom1", + "Test Domain1", "testOrg", adminUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + Policy policy1 = createPolicyObject(client, "PolicyDelDom1", "Policy1"); + client.putPolicy("PolicyDelDom1", "Policy1", AUDIT_REF, policy1); + + Policy policy2 = createPolicyObject(client, "PolicyDelDom1", "Policy2"); + client.putPolicy("PolicyDelDom1", "Policy2", AUDIT_REF, policy2); + + Policy policyRes1 = client.getPolicy("PolicyDelDom1", "Policy1"); + assertNotNull(policyRes1); + + Policy policyRes2 = client.getPolicy("PolicyDelDom1", "Policy2"); + assertNotNull(policyRes2); + + client.deletePolicy("PolicyDelDom1", "Policy1", AUDIT_REF); + + // we need to get an exception here + try { + client.getPolicy("PolicyDelDom1", "Policy1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + policyRes2 = client.getPolicy("PolicyDelDom1", "Policy2"); + assertNotNull(policyRes2); + + client.deletePolicy("PolicyDelDom1", "Policy2", AUDIT_REF); + + // we need to get an exception here + try { + client.getPolicy("PolicyDelDom1", "Policy1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + // we need to get an exception here + try { + client.getPolicy("PolicyDelDom1", "Policy2"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + client.deleteTopLevelDomain("PolicyDelDom1", AUDIT_REF); + } + + private void testCreateServiceIdentity(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceAddDom1", + "Test Domain1", "testOrg", adminUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + ServiceIdentity service = createServiceObject(client, "ServiceAddDom1", "Service1", + "http://localhost", "/usr/bin/java", "root", "users", "host1"); + + client.putServiceIdentity("ServiceAddDom1", "Service1", AUDIT_REF, service); + + ServiceIdentity serviceRes2 = client.getServiceIdentity("ServiceAddDom1", "Service1"); + assertNotNull(serviceRes2); + assertEquals(serviceRes2.getName(), "ServiceAddDom1.Service1".toLowerCase()); + + client.deleteTopLevelDomain("ServiceAddDom1", AUDIT_REF); + } + + private void testDeletePublicKeyEntry(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("DelPublicKeyDom1", + "Test Domain1", "testOrg", adminUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + ServiceIdentity service = createServiceObjectWithZoneKeys(client, "DelPublicKeyDom1", "Service1", + "http://localhost", "/usr/bin/java", "root", "users", "host1"); + + client.putServiceIdentity("DelPublicKeyDom1", "Service1", AUDIT_REF, service); + + client.deletePublicKeyEntry("DelPublicKeyDom1", "Service1", "zone1", AUDIT_REF); + + try { + client.getPublicKeyEntry("DelPublicKeyDom1", "Service1", "zone1"); + fail(); + } catch (ZMSClientException ex) { + assertEquals(ex.getCode(), 404); + } + + PublicKeyEntry entry = client.getPublicKeyEntry("DelPublicKeyDom1", "Service1", "zone2"); + assertNotNull(entry); + assertEquals(entry.getKey(), PUB_KEY_ZONE2); + + // we are not allowed to delete the last public key + + try { + client.deletePublicKeyEntry("DelPublicKeyDom1", "Service1", "zone2", AUDIT_REF); + fail(); + } catch (ZMSClientException ex) { + assertEquals(ex.getCode(), 400); + } + + client.deleteTopLevelDomain("DelPublicKeyDom1", AUDIT_REF); + } + + private void testCreateEntity(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("CreateEntityDom1", + "Test Domain1", "testOrg", adminUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + Entity entity1 = createEntityObject(client, "Entity1"); + client.putEntity("CreateEntityDom1", "Entity1", AUDIT_REF, entity1); + + Entity entity2 = client.getEntity("CreateEntityDom1", "Entity1"); + assertNotNull(entity2); + assertEquals(entity2.getName(), "Entity1".toLowerCase()); + + client.deleteTopLevelDomain("CreateEntityDom1", AUDIT_REF); + } + + private void testDeleteEntity(ZMSClient client, String adminUser) { + + TopLevelDomain dom1 = createTopLevelDomainObject("DelEntityDom1", + "Test Domain1", "testOrg", adminUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + Entity entity1 = createEntityObject(client, "Entity1"); + client.putEntity("DelEntityDom1", "Entity1", AUDIT_REF, entity1); + + Entity entity2 = createEntityObject(client, "Entity2"); + client.putEntity("DelEntityDom1", "Entity2", AUDIT_REF, entity2); + + Entity entityRes = client.getEntity("DelEntityDom1", "Entity1"); + assertNotNull(entityRes); + + entityRes = client.getEntity("DelEntityDom1", "Entity2"); + assertNotNull(entityRes); + + client.deleteEntity("DelEntityDom1", "Entity1", AUDIT_REF); + + try { + entityRes = client.getEntity("DelEntityDom1", "Entity1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + entityRes = client.getEntity("DelEntityDom1", "Entity2"); + assertNotNull(entityRes); + + client.deleteEntity("DelEntityDom1", "Entity2", AUDIT_REF); + + try { + entityRes = client.getEntity("DelEntityDom1", "Entity1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + try { + entityRes = client.getEntity("DelEntityDom1", "Entity2"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + client.deleteTopLevelDomain("DelEntityDom1", AUDIT_REF); + } + + // Unit Tests for ZMS Java Client + + @Test + public void testClientNoAuth() { + ZMSClient client = new ZMSClient("http://localhost:10080/zms/v1"); + assertNotNull(client); + } + + @Test + public void testClientOnlyUrl() { + String zmsUrl = getZMSUrl(); + ZMSClient client = new ZMSClient(zmsUrl); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + assertNotNull(client); + + // verify we can get domain list + DomainList domainListMock = Mockito.mock(DomainList.class); + Mockito.when(client.getDomainList()).thenReturn(domainListMock).thenThrow(new ResourceException(400)); + DomainList domList = client.getDomainList(); + assertNotNull(domList); + try { + client.getDomainList(); + fail(); + } catch(ResourceException ex) { + assertTrue(true); + } + + // verify we can't add a domain + + TopLevelDomain dom1 = createTopLevelDomainObject("OnlyUrlDomain", + "Test Domain1", "testOrg", systemAdminFullUser); + try { + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1)).thenThrow(new ResourceException(400)); + client.postTopLevelDomain(AUDIT_REF, dom1); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + } + + @Test + public void testClientInvalidPort() { + String zmsUrl = "http://localhost:11080/zms/v1"; + ZMSClient client = new ZMSClient(zmsUrl); + assertNotNull(client); + + // verify we can't get domain list and try some + // other operations which should all return + // zms client exceptions + + try { + client.getDomainList(); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + + try { + TopLevelDomain dom1 = createTopLevelDomainObject("OnlyUrlDomain", + "Test Domain1", "testOrg", systemAdminFullUser); + + client.postTopLevelDomain(AUDIT_REF, dom1); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + + try { + client.getAccess("UPDATE", "AccessDom1:resource1", "AccessDom1"); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + } + + @Test + public void testClientUrlPrincipal() { + + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + assertNotNull(client); + + // verify we can get domain list + DomainList domainListMock = Mockito.mock(DomainList.class); + Mockito.when(client.getDomainList()).thenReturn(domainListMock); + DomainList domList = client.getDomainList(); + assertNotNull(domList); + + // verify we can add a domain + + TopLevelDomain dom1 = createTopLevelDomainObject("UrlPrincipalDomain", + "Test Domain1", "testOrg", systemAdminFullUser); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1)).thenReturn(domainMock); + Domain resDom1 = client.postTopLevelDomain(AUDIT_REF, dom1); + assertNotNull(resDom1); + + client.deleteTopLevelDomain("UrlPrincipalDomain", AUDIT_REF); + } + + @Test + public void testClientClearPrincipal() { + String zmsUrl = getZMSUrl(); + ZMSClient client = new ZMSClient(zmsUrl); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + assertNotNull(client); + + // add credentials + + Authority authority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + Principal p = SimplePrincipal.create("user", systemAdminUser, + "v=U1;d=user;n=" + systemAdminUser + ";s=signature", 0, authority); + + client.addCredentials(p); + + // add credentials again + + client.addCredentials(p); + + // verify we can add a domain + + TopLevelDomain dom1 = createTopLevelDomainObject("ClearPrincipalDomain", + "Test Domain1", "testOrg", systemAdminFullUser); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1)).thenReturn(domainMock); + Domain resDom1 = client.postTopLevelDomain(AUDIT_REF, dom1); + assertNotNull(resDom1); + + client.deleteTopLevelDomain("ClearPrincipalDomain", AUDIT_REF); + + // clear the credentials + + client.clearCredentials(); + + // verify we can no longer add a new domain + + try { + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1)).thenThrow(new ResourceException(400)); + client.postTopLevelDomain(AUDIT_REF, dom1); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + + // but we should be able to read the domain list + DomainList domainListMock = Mockito.mock(DomainList.class); + Mockito.when(client.getDomainList()).thenReturn(domainListMock); + DomainList domList = client.getDomainList(); + assertNotNull(domList); + } + + @Test + public void testClientWithoutEndingSlash() { + String zmsUrl = getZMSUrl(); + if (zmsUrl == null) { + zmsUrl = "http://localhost:10080"; + } else { + if (zmsUrl.charAt(zmsUrl.length() - 1) == '/') { + zmsUrl = zmsUrl.substring(0, zmsUrl.length() - 1); + } + } + + ZMSClient client = new ZMSClient(zmsUrl); + assertNotNull(client); + + // verify we should be able to read the domain list + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + DomainList domainListMock = Mockito.mock(DomainList.class); + Mockito.when(c.getDomainList(null, null, null, null, null, null, null, null, null)).thenReturn(domainListMock); + DomainList domList = client.getDomainList(); + assertNotNull(domList); + } + + @Test + public void testGetDomainList() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + try { + Mockito.when(c.getDomainList(null, null, null, null, null, null, "MemberRole1", "RoleName1", null)) + .thenThrow(new NullPointerException()); + client.getDomainList("MemberRole1", "RoleName1"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getDomainList(null, null, null, null, null, null, "MemberRole2", "RoleName2", null)) + .thenThrow(new ResourceException(400)); + client.getDomainList("MemberRole2", "RoleName2"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteSubDomain() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + try { + Mockito.when(c.deleteSubDomain("parent", "domain1", AUDIT_REF)).thenThrow(new NullPointerException()); + client.deleteSubDomain("parent", "domain1", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.deleteSubDomain("parent", "domain2", AUDIT_REF)).thenThrow(new ResourceException(400)); + client.deleteSubDomain("parent", "domain2", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testPutTenancyResourceGroup() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TenancyResourceGroup trg = new TenancyResourceGroup().setDomain("test.domain").setService("test-service") + .setResourceGroup("test.group"); + try { + Mockito.when( + c.putTenancyResourceGroup("TenantDom1", "DelTenantRolesDom1", "ResourceGroup1", AUDIT_REF, trg)) + .thenThrow(new NullPointerException()); + client.putTenancyResourceGroup("TenantDom1", "DelTenantRolesDom1", "ResourceGroup1", AUDIT_REF, trg); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when( + c.putTenancyResourceGroup("TenantDom2", "DelTenantRolesDom1", "ResourceGroup1", AUDIT_REF, trg)) + .thenThrow(new ResourceException(400)); + client.putTenancyResourceGroup("TenantDom2", "DelTenantRolesDom1", "ResourceGroup1", AUDIT_REF, trg); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testPutTenantResourceGroupRoles() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TenantResourceGroupRoles tenantRoles = new TenantResourceGroupRoles(); + try { + Mockito.when(c.putTenantResourceGroupRoles("ProvidorDomain1", "ProvidorService1", "TenantDom1", + "ResourceGroup1", AUDIT_REF, tenantRoles)).thenThrow(new NullPointerException()); + client.putTenantResourceGroupRoles("ProvidorDomain1", "ProvidorService1", "TenantDom1", "ResourceGroup1", + AUDIT_REF, tenantRoles); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.putTenantResourceGroupRoles("ProvidorDomain2", "ProvidorService1", "TenantDom1", + "ResourceGroup1", AUDIT_REF, tenantRoles)).thenThrow(new ResourceException(400)); + client.putTenantResourceGroupRoles("ProvidorDomain2", "ProvidorService1", "TenantDom1", "ResourceGroup1", + AUDIT_REF, tenantRoles); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetTenantResourceGroupRoles() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + try { + Mockito.when(c.getTenantResourceGroupRoles("ProvidorDomain1", "ProvidorService1", "TenantDom1", + "ResourceGroup1")).thenThrow(new NullPointerException()); + client.getTenantResourceGroupRoles("ProvidorDomain1", "ProvidorService1", "TenantDom1", "ResourceGroup1"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getTenantResourceGroupRoles("ProvidorDomain2", "ProvidorService1", "TenantDom1", + "ResourceGroup1")).thenThrow(new ResourceException(400)); + client.getTenantResourceGroupRoles("ProvidorDomain2", "ProvidorService1", "TenantDom1", "ResourceGroup1"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteTenancyResourceGroup() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + try { + Mockito.when(c.deleteTenancyResourceGroup("TenantDom1", "DelTenantRolesDom1", "ResourceGroup1", AUDIT_REF)) + .thenThrow(new NullPointerException()); + client.deleteTenancyResourceGroup("TenantDom1", "DelTenantRolesDom1", "ResourceGroup1", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.deleteTenancyResourceGroup("TenantDom2", "DelTenantRolesDom1", "ResourceGroup1", AUDIT_REF)) + .thenThrow(new ResourceException(400)); + client.deleteTenancyResourceGroup("TenantDom2", "DelTenantRolesDom1", "ResourceGroup1", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetRoles() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + try { + Mockito.when(c.getRoles("domain1", true)).thenThrow(new NullPointerException()); + client.getRoles("domain1", true); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getRoles("domain2", true)).thenThrow(new ResourceException(400)); + client.getRoles("domain2", true); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetPolicies() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + try { + Mockito.when(c.getPolicies("domain1", true)).thenThrow(new NullPointerException()); + client.getPolicies("domain1", true); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getPolicies("domain2", true)).thenThrow(new ResourceException(400)); + client.getPolicies("domain2", true); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteUserDomain() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + try { + Mockito.when(c.deleteUserDomain("domain1", AUDIT_REF)).thenThrow(new NullPointerException()); + client.deleteUserDomain("domain1", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.deleteUserDomain("domain2", AUDIT_REF)).thenThrow(new ResourceException(400)); + client.deleteUserDomain("domain2", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testPutDomainMeta() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + DomainMeta meta = createDomainMetaObject("Test2 Domain", "NewOrg", false); + try { + Mockito.when(c.putDomainMeta("domain1", AUDIT_REF, meta)).thenThrow(new NullPointerException()); + client.putDomainMeta("domain1", AUDIT_REF, meta); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.putDomainMeta("domain2", AUDIT_REF, meta)).thenThrow(new ResourceException(400)); + client.putDomainMeta("domain2", AUDIT_REF, meta); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testPutDomainTemplate() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + List tempNames = new ArrayList(); + DomainTemplate domTempl = new DomainTemplate().setTemplateNames(tempNames); + try { + Mockito.when(c.putDomainTemplate("name1", AUDIT_REF, domTempl)).thenThrow(new NullPointerException()); + client.putDomainTemplate("name1", AUDIT_REF, domTempl); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.putDomainTemplate("name2", AUDIT_REF, domTempl)) + .thenThrow(new ResourceException(404, "Domain not found")); + client.putDomainTemplate("name2", AUDIT_REF, domTempl); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetResourceAccessList() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + try { + Mockito.when(c.getResourceAccessList("principal1", "action1")).thenThrow(new NullPointerException()); + client.getResourceAccessList("principal1", "action1"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getResourceAccessList("principal2", "action2")).thenThrow(new ResourceException(400)); + client.getResourceAccessList("principal2", "action2"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testAssertion() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Assertion assertion = new Assertion(); + Long assertionId = 18000305032230531L; + try { + Mockito.when(c.putAssertion("domain1", "policy1", AUDIT_REF, assertion)) + .thenThrow(new NullPointerException()); + client.putAssertion("domain1", "policy1", AUDIT_REF, assertion); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getAssertion("principal2", "action2", assertionId)).thenThrow(new NullPointerException()); + client.getAssertion("principal2", "action2", assertionId); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.deleteAssertion("principal2", "action2", assertionId, AUDIT_REF)) + .thenThrow(new NullPointerException()); + client.deleteAssertion("principal2", "action2", assertionId, AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetTemplate() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + try { + Mockito.when(c.getTemplate("template")).thenThrow(new NullPointerException()); + client.getTemplate("template"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getTemplate("template2")).thenThrow(new ResourceException(400)); + client.getTemplate("template2"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetProviderResourceGroupRoles() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + try { + Mockito.when(c.getProviderResourceGroupRoles("tenantDomain", "providerDomain", "providerServiceName", + "resourceGroup")).thenThrow(new NullPointerException()); + client.getProviderResourceGroupRoles("tenantDomain", "providerDomain", "providerServiceName", + "resourceGroup"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getProviderResourceGroupRoles("tenantDomain2", "providerDomain2", "providerServiceName2", + "resourceGroup2")).thenThrow(new ResourceException(400)); + client.getProviderResourceGroupRoles("tenantDomain2", "providerDomain2", "providerServiceName2", + "resourceGroup2"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteProviderResourceGroupRoles() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + try { + Mockito.when(c.deleteProviderResourceGroupRoles("tenantDomain", "providerDomain", "providerServiceName", + "resourceGroup", AUDIT_REF)).thenThrow(new NullPointerException()); + client.deleteProviderResourceGroupRoles("tenantDomain", "providerDomain", "providerServiceName", + "resourceGroup", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.deleteProviderResourceGroupRoles("tenantDomain2", "providerDomain2", "providerServiceName2", + "resourceGroup2", AUDIT_REF)).thenThrow(new ResourceException(400)); + client.deleteProviderResourceGroupRoles("tenantDomain2", "providerDomain2", "providerServiceName2", + "resourceGroup2", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testPutProviderResourceGroupRoles() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + ProviderResourceGroupRoles provRoles = new ProviderResourceGroupRoles(); + try { + Mockito.when(c.putProviderResourceGroupRoles("tenantDomain", "providerDomain", "providerServiceName", + "resourceGroup", AUDIT_REF, provRoles)).thenThrow(new NullPointerException()); + client.putProviderResourceGroupRoles("tenantDomain", "providerDomain", "providerServiceName", + "resourceGroup", AUDIT_REF, provRoles); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.putProviderResourceGroupRoles("tenantDomain2", "providerDomain2", "providerServiceName2", + "resourceGroup2", AUDIT_REF, provRoles)).thenThrow(new ResourceException(400)); + client.putProviderResourceGroupRoles("tenantDomain2", "providerDomain2", "providerServiceName2", + "resourceGroup2", AUDIT_REF, provRoles); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testPostUserDomain() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + UserDomain ud = new UserDomain().setDescription("domain desc").setOrg("org:test").setEnabled(true) + .setAuditEnabled(false).setAccount("user.test").setYpmId(10).setName("testuser") + .setTemplates(new DomainTemplateList().setTemplateNames(Arrays.asList("template"))); + try { + Mockito.when(c.postUserDomain("domain1", AUDIT_REF, ud)).thenThrow(new NullPointerException()); + client.postUserDomain("domain1", AUDIT_REF, ud); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.postUserDomain("domain2", AUDIT_REF, ud)).thenThrow(new ResourceException(400)); + client.postUserDomain("domain2", AUDIT_REF, ud); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testUserTokenWithAuthority() { + ZMSClient client = createClient(systemAdminUser); + assertNotNull(client); + } + + @Test + public void testCreateTopLevelDomainUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1 = createTopLevelDomainObject("AddTopDom1", + "Test Domain1", "testOrg", systemAdminFullUser); + Domain domainMock = Mockito.mock(Domain.class); + dom1.setAuditEnabled(true); + Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class))).thenReturn(domainMock); + Mockito.when(c.getDomain("AddTopDom1")).thenReturn(domainMock); + Mockito.when(c.getDomain("AddTopDom3")).thenThrow(new NullPointerException()).thenThrow(new ResourceException(204));; + testCreateTopLevelDomain(client, systemAdminFullUser); + } + + @Test + public void testCreateTopLevelDomainOnceOnlyUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1 = createTopLevelDomainObject("AddTopDom1", + "Test Domain1", "testOrg", systemAdminFullUser); + Domain domainMock = Mockito.mock(Domain.class); + dom1.setAuditEnabled(true); + Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class))).thenReturn(domainMock).thenThrow(new ResourceException(204)); + testCreateTopLevelDomainOnceOnly(client, systemAdminFullUser); + } + + @Test + public void testCreateSubDomainUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1Mock = Mockito.mock(TopLevelDomain.class); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1Mock)).thenReturn(domainMock); + SubDomain dom2 = createSubDomainObject("AddSubDom2", "AddSubDom1", + "Test Domain2", "testOrg", systemAdminFullUser); + Mockito.when(c.postSubDomain("AddSubDom1", AUDIT_REF, dom2)).thenReturn(domainMock); + Mockito.when(c.getDomain("AddSubDom1.AddSubDom2")).thenReturn(domainMock); + Mockito.when(c.postSubDomain("AddSubDom3", AUDIT_REF, dom2)).thenThrow(new NullPointerException()); + testCreateSubDomain(client, systemAdminFullUser); + } + + @Test + public void testCreateSubdomainOnceOnlyUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1Mock = Mockito.mock(TopLevelDomain.class); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1Mock)).thenReturn(domainMock); + SubDomain dom2 = createSubDomainObject("AddOnceSubDom2", "AddOnceSubDom1", + "Test Domain2", "testOrg", systemAdminFullUser); + Mockito.when(c.postSubDomain("AddOnceSubDom1", AUDIT_REF, dom2)).thenReturn(domainMock).thenThrow(new ResourceException(204)); + testCreateSubdomainOnceOnly(client, systemAdminFullUser); + } + + @Test + public void testCreateRoleUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1Mock = Mockito.mock(TopLevelDomain.class); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1Mock)).thenReturn(domainMock); + Role role1Mock = Mockito.mock(Role.class); + Role role1 = createRoleObject(client, "CreateRoleDom1", "Role1", null, "user.joe", "user.jane"); + Mockito.when(c.putRole("CreateRoleDom1", "Role1", AUDIT_REF, role1Mock)).thenReturn(role1Mock); + Mockito.when(c.getRole("CreateRoleDom1", "Role1", false, false)).thenReturn(role1Mock); + Mockito.when(role1Mock.getName()).thenReturn("CreateRoleDom1:role.Role1".toLowerCase()); + Mockito.when(role1Mock.getTrust()).thenReturn(null); + Mockito.when(c.putRole("CreateRoleDom1", "Role2", AUDIT_REF, role1)).thenThrow(new NullPointerException()); + Mockito.when(c.putRole("CreateRoleDom1", "Role3", AUDIT_REF, role1)).thenThrow(new ResourceException(400)); + Mockito.when(c.getRole("CreateRoleDom1", "Role2", false, false)).thenThrow(new NullPointerException()); + Mockito.when(c.getRole("CreateRoleDom1", "Role3", false, false)).thenThrow(new ResourceException(400)); + testCreateRole(client, systemAdminFullUser); + } + + @Test + public void testGetRoleList() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + RoleList roleListMock = Mockito.mock(RoleList.class); + Mockito.when(c.getRoleList("RoleListParamDom1", null, "Role1")).thenReturn(roleListMock); + RoleList roleList = client.getRoleList("RoleListParamDom1", null, "Role1"); + assertNotNull(roleList); + try { + Mockito.when(c.getRoleList("RoleListParamDom1", null, "Role2")).thenThrow(new ResourceException(204)); + client.getRoleList("RoleListParamDom1", null, "Role2"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getRoleList("RoleListParamDom2", null, "Role2")).thenThrow(new NullPointerException()); + client.getRoleList("RoleListParamDom2", null, "Role2"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getRoleList("RoleListParamDom1", null, null)).thenThrow(new ResourceException(204)); + client.getRoleList("RoleListParamDom1"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getRoleList("RoleListParamDom2", null, null)).thenThrow(new NullPointerException()); + client.getRoleList("RoleListParamDom2"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteRole() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Role roleMock = Mockito.mock(Role.class); + Mockito.when(c.deleteRole("DelRoleDom1", "Role1", AUDIT_REF)).thenReturn(roleMock); + client.deleteRole("DelRoleDom1", "Role1", AUDIT_REF); + try { + Mockito.when(c.deleteRole("DelRoleDom1", "Role2", AUDIT_REF)).thenThrow(new ResourceException(204)); + client.deleteRole("DelRoleDom1", "Role2", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.deleteRole("DelRoleDom2", "Role2", AUDIT_REF)).thenThrow(new NullPointerException()); + client.deleteRole("DelRoleDom2", "Role2", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetMembership() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Membership member1Mock = Mockito.mock(Membership.class); + Mockito.when(c.getMembership("MbrGetRoleDom1", "Role1", "user.joe")).thenReturn(member1Mock); + client.getMembership("MbrGetRoleDom1", "Role1", "user.doe"); + try { + Mockito.when(c.getMembership("MbrGetRoleDom1", "Role2", "user.joe")).thenThrow(new ResourceException(204)); + client.getMembership("MbrGetRoleDom1", "Role2", "user.joe"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + + try { + Mockito.when(c.getMembership("MbrGetRoleDom1", "Role3", "user.joe")).thenThrow(new NullPointerException()); + client.getMembership("MbrGetRoleDom1", "Role3", "user.joe"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetPolicyList() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + PolicyList policyListMock = Mockito.mock(PolicyList.class); + Mockito.when(c.getPolicyList("PolicyListDom1", null, null)).thenReturn(policyListMock); + client.getPolicyList("PolicyListDom1", null, null); + try { + Mockito.when(c.getPolicyList("PolicyListDom2", null, null)).thenThrow(new ResourceException(204)); + client.getPolicyList("PolicyListDom2", null, null); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getPolicyList("PolicyListDom3", null, null)).thenThrow(new ResourceException(204)); + client.getPolicyList("PolicyListDom3"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + + try { + Mockito.when(c.getPolicyList("PolicyListDom4", null, null)).thenThrow(new NullPointerException()); + client.getPolicyList("PolicyListDom4"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getPolicyList("PolicyListDom5", null, null)).thenThrow(new NullPointerException()); + client.getPolicyList("PolicyListDom5", null, null); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteServiceIdentity() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + ServiceIdentity serviceMock = Mockito.mock(ServiceIdentity.class); + Mockito.when(c.deleteServiceIdentity("ServiceDelDom1", "Service1", AUDIT_REF)).thenReturn(serviceMock); + client.deleteServiceIdentity("ServiceDelDom1", "Service1", AUDIT_REF); + try { + Mockito.when(c.deleteServiceIdentity("ServiceDelDom1", "Service2", AUDIT_REF)).thenThrow(new ResourceException(204)); + client.deleteServiceIdentity("ServiceDelDom1", "Service2", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + Mockito.when(c.deleteServiceIdentity("ServiceDelDom2", "Service2", AUDIT_REF)) + .thenThrow(new NullPointerException()); + client.deleteServiceIdentity("ServiceDelDom2", "Service2", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetServiceIdentityList() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + ServiceIdentityList serviceListMock = Mockito.mock(ServiceIdentityList.class); + Mockito.when(c.getServiceIdentityList("ServiceListParamsDom1", null, "Service1")).thenReturn(serviceListMock); + client.getServiceIdentityList("ServiceListParamsDom1", null, "Service1"); + try { + Mockito.when(c.getServiceIdentityList("ServiceListParamsDom2", null, "Service1")).thenThrow(new ResourceException(204)); + client.getServiceIdentityList("ServiceListParamsDom2", null, "Service1"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + + Mockito.when(c.getServiceIdentityList("ServiceListParamsDom3", null, null)).thenReturn(serviceListMock); + client.getServiceIdentityList("ServiceListParamsDom3"); + try { + Mockito.when(c.getServiceIdentityList("ServiceListParamsDom4", null, null)).thenThrow(new ResourceException(204)); + client.getServiceIdentityList("ServiceListParamsDom4"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testPutPublicKeyEntry() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + PublicKeyEntry keyEntry = new PublicKeyEntry(); + PublicKeyEntry keyEntryMock = Mockito.mock(PublicKeyEntry.class); + Mockito.when(c.putPublicKeyEntry("PutPublicKeyDom2", "Service1", "zone2", AUDIT_REF, keyEntry)).thenReturn(keyEntryMock); + client.putPublicKeyEntry("PutPublicKeyDom2", "Service1", "zone2", AUDIT_REF, keyEntry); + + try { + Mockito.when(c.putPublicKeyEntry("PutPublicKeyDom3", "Service2", "zone2", AUDIT_REF, keyEntry)).thenThrow(new ResourceException(204)); + client.putPublicKeyEntry("PutPublicKeyDom3", "Service2", "zone2", AUDIT_REF, keyEntry); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetTenancy() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Tenancy tenancyMock = Mockito.mock(Tenancy.class); + Mockito.when(c.getTenancy("tenantDom1", "providerService1")).thenReturn(tenancyMock).thenThrow(new ZMSClientException(400,"Audit reference required")); + client.getTenancy("tenantDom1", "providerService1"); + try { + client.getTenancy("tenantDom1", "providerService1"); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getTenancy("tenantDom2", "providerService1")).thenThrow(new NullPointerException()); + client.getTenancy("tenantDom2", "providerService1"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteTenancy() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Tenancy tenancyMock = Mockito.mock(Tenancy.class); + Mockito.when(c.deleteTenancy("tenantDom1", "providerService1", AUDIT_REF)).thenReturn(tenancyMock).thenThrow(new ZMSClientException(400,"Audit reference required")); + client.deleteTenancy("tenantDom1", "providerService1", AUDIT_REF); + try { + client.deleteTenancy("tenantDom1", "providerService1", AUDIT_REF); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + try { + Mockito.when(c.deleteTenancy("tenantDom2", "providerService1", AUDIT_REF)) + .thenThrow(new NullPointerException()); + client.deleteTenancy("tenantDom2", "providerService1", AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testPutTenantRoles() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TenantRoles tenantRoleMock = Mockito.mock(TenantRoles.class); + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()) + .setAction((String) f.value())); + } + TenantRoles tenantRoles = new TenantRoles().setDomain("ProviderDomain1").setService("storage").setTenant("TenantDomain1").setRoles(roleActions); + Mockito.when(c.putTenantRoles("ProviderDomain1", "ProviderService1", "TenantDomain1", AUDIT_REF, tenantRoles)).thenReturn(tenantRoleMock).thenThrow(new ZMSClientException(400,"Audit reference required")); + client.putTenantRoles("ProviderDomain1", "ProviderService1", "TenantDomain1", AUDIT_REF, tenantRoles); + try { + client.putTenantRoles("ProviderDomain1", "ProviderService1", "TenantDomain1", AUDIT_REF, tenantRoles); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + try { + Mockito.when( + c.putTenantRoles("ProviderDomain2", "ProviderService1", "TenantDomain1", AUDIT_REF, tenantRoles)) + .thenThrow(new NullPointerException()); + client.putTenantRoles("ProviderDomain2", "ProviderService1", "TenantDomain1", AUDIT_REF, tenantRoles); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetTenantRoles() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TenantRoles tenantRoleMock = Mockito.mock(TenantRoles.class); + Mockito.when(c.getTenantRoles("ProviderDomain1", "ProviderService1", "TenantDomain1")).thenReturn(tenantRoleMock).thenThrow(new ZMSClientException(400,"Audit reference required")); + client.getTenantRoles("ProviderDomain1", "ProviderService1", "TenantDomain1"); + try { + client.getTenantRoles("ProviderDomain1", "ProviderService1", "TenantDomain1"); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getTenantRoles("ProviderDomain2", "ProviderService1", "TenantDomain1")) + .thenThrow(new NullPointerException()); + client.getTenantRoles("ProviderDomain2", "ProviderService1", "TenantDomain1"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteTenantRoles() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TenantRoles tenantRoleMock = Mockito.mock(TenantRoles.class); + Mockito.when(c.deleteTenantRoles("ProviderDomain1", "ProviderService1", "TenantDomain1", AUDIT_REF)).thenReturn(tenantRoleMock).thenThrow(new ZMSClientException(400,"Audit reference required")); + client.deleteTenantRoles("ProviderDomain1", "ProviderService1", "TenantDomain1", AUDIT_REF); + try { + client.deleteTenantRoles("ProviderDomain1", "ProviderService1", "TenantDomain1", AUDIT_REF); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + } + + @Test + public void testGetSignedDomains() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Map> respHdrs = new HashMap>(); + SignedDomains signedDomain1 = Mockito.mock(SignedDomains.class); + Mockito.when(c.getSignedDomains("dom1", "meta1", "tag1", respHdrs)).thenReturn(signedDomain1).thenThrow(new ZMSClientException(400,"Audit reference required")); + client.getSignedDomains("dom1", "meta1", "tag1", respHdrs); + try { + client.getSignedDomains("dom1", "meta1", "tag1", respHdrs); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + } + + @Test + public void testPutDefaultAdmins() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + DefaultAdmins adminsMock = Mockito.mock(DefaultAdmins.class); + Mockito.when(c.putDefaultAdmins("sports", AUDIT_REF, adminsMock)).thenReturn(adminsMock); + client.putDefaultAdmins("sports", AUDIT_REF, adminsMock); + try { + Mockito.when(c.putDefaultAdmins("media", AUDIT_REF, adminsMock)).thenThrow(new ZMSClientException(400,"Audit reference required")); + client.putDefaultAdmins("media", AUDIT_REF, adminsMock); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + } + + @Test + public void testGetDomainDataCheck() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + DomainDataCheck checkdom1 = Mockito.mock(DomainDataCheck.class); + Mockito.when(c.getDomainDataCheck("domain1")).thenReturn(checkdom1).thenThrow(new ZMSClientException(400,"Audit reference required")); + client.getDomainDataCheck("domain1"); + try { + client.getDomainDataCheck("domain1"); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + try { + Mockito.when(c.getDomainDataCheck("domain2")).thenThrow(new NullPointerException()); + client.getDomainDataCheck("domain2"); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + } + + @Test + public void testPutTenancy() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Tenancy tenancyMock = Mockito.mock(Tenancy.class); + Tenancy tenant = createTenantObject("tenantDom1", "providerDom1" + "." + "providerService1"); + Mockito.when(c.putTenancy("tenantDom1", "providerService1", AUDIT_REF, tenant)).thenReturn(tenancyMock).thenThrow(new ZMSClientException(400,"Audit reference required")); + client.putTenancy("tenantDom1", "providerService1", AUDIT_REF, tenant); + try { + client.putTenancy("tenantDom1", "providerService1", AUDIT_REF, tenant); + fail(); + } catch (ZMSClientException ex) { + assertTrue(true); + } + try { + Mockito.when(c.putTenancy("tenantDom2", "providerService1", AUDIT_REF, tenant)) + .thenThrow(new NullPointerException()); + client.putTenancy("tenantDom2", "providerService1", AUDIT_REF, tenant); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testAddMembershipUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1Mock = Mockito.mock(TopLevelDomain.class); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1Mock)).thenReturn(domainMock); + Role role1Mock = Mockito.mock(Role.class); + Role roleMock = Mockito.mock(Role.class); + Mockito.when(c.putRole("MbrAddDom1", "Role1", AUDIT_REF, role1Mock)).thenReturn(roleMock); + Membership mbr = new Membership(); + mbr.setRoleName("Role1"); + mbr.setMemberName("user.doe"); + mbr.setIsMember(true); + Membership membershipMock = Mockito.mock(Membership.class); + Mockito.when(c.putMembership("MbrAddDom1", "Role1", "user.doe", AUDIT_REF, mbr)).thenReturn(membershipMock); + Mockito.when(c.getRole("MbrAddDom1", "Role1", false, false)).thenReturn(roleMock); + @SuppressWarnings("unchecked") + List membersMock = Mockito.mock(List.class); + Mockito.when(roleMock.getMembers()).thenReturn(membersMock); + Mockito.when(membersMock.size()).thenReturn(3); + Mockito.when(membersMock.contains(Mockito.anyString())).thenReturn(true); + testAddMembership(client, systemAdminFullUser); + Mockito.when(c.deleteTopLevelDomain("MbrGetRoleDom1", AUDIT_REF)).thenReturn(dom1Mock); + } + + @Test + public void testDeleteMembershipUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1Mock = Mockito.mock(TopLevelDomain.class); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1Mock)).thenReturn(domainMock); + Role role1Mock = Mockito.mock(Role.class); + Mockito.when(c.putRole("MbrDelDom1", "Role1", AUDIT_REF, role1Mock)).thenReturn(role1Mock); + Mockito.when(c.getRole("MbrDelDom1", "Role1", false, false)).thenReturn(role1Mock); + @SuppressWarnings("unchecked") + List membersMock = Mockito.mock(List.class); + Mockito.when(role1Mock.getMembers()).thenReturn(membersMock); + Mockito.when(membersMock.size()).thenReturn(1); + Mockito.when(membersMock.contains("user.joe")).thenReturn(false); + Mockito.when(membersMock.contains("user.jane")).thenReturn(true); + testDeleteMembership(client, systemAdminFullUser); + } + + @Test + public void testCreatePolicyUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1Mock = Mockito.mock(TopLevelDomain.class); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1Mock)).thenReturn(domainMock); + Policy policy1Mock = Mockito.mock(Policy.class); + Mockito.when(c.putPolicy("PolicyAddDom1", "Policy1", AUDIT_REF, policy1Mock)).thenReturn(policy1Mock); + Mockito.when(c.getPolicy("PolicyAddDom1", "Policy1")).thenReturn(policy1Mock); + Mockito.when(policy1Mock.getName()).thenReturn("PolicyAddDom1:policy.Policy1".toLowerCase()); + testCreatePolicy(client, systemAdminFullUser); + } + + @Test + public void testDeletePolicyUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1Mock = Mockito.mock(TopLevelDomain.class); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1Mock)).thenReturn(domainMock); + Policy policy1Mock = Mockito.mock(Policy.class); + Mockito.when(c.putPolicy("PolicyDelDom1", "Policy1", AUDIT_REF, policy1Mock)).thenReturn(policy1Mock); + Mockito.when(c.putPolicy("PolicyDelDom1", "Policy2", AUDIT_REF, policy1Mock)).thenReturn(policy1Mock); + Mockito.when(c.getPolicy("PolicyDelDom1", "Policy1")).thenReturn(policy1Mock).thenThrow(new ResourceException(204)); + Mockito.when(c.getPolicy("PolicyDelDom1", "Policy2")).thenReturn(policy1Mock,policy1Mock).thenThrow(new ResourceException(204)); + testDeletePolicy(client, systemAdminFullUser); + } + + @Test + public void testDeletePublicKeyEntryUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class))).thenReturn(domainMock); + ServiceIdentity serviceMock = Mockito.mock(ServiceIdentity.class); + Mockito.when(c.putServiceIdentity("DelPublicKeyDom1", "Service1", AUDIT_REF, serviceMock)).thenReturn(serviceMock); + PublicKeyEntry entoryMock = Mockito.mock(PublicKeyEntry.class); + Mockito.when(c.deletePublicKeyEntry("DelPublicKeyDom1", "Service1", "zone1", AUDIT_REF)).thenReturn(entoryMock); + Mockito.when(c.getPublicKeyEntry("DelPublicKeyDom1", "Service1", "zone1")).thenThrow(new ResourceException(404)); + Mockito.when(c.getPublicKeyEntry("DelPublicKeyDom1", "Service1", "zone2")).thenReturn(entoryMock); + Mockito.when(entoryMock.getKey()).thenReturn(PUB_KEY_ZONE2); + Mockito.when(c.deletePublicKeyEntry("DelPublicKeyDom1", "Service1", "zone2", AUDIT_REF)).thenThrow(new ResourceException(400)); + testDeletePublicKeyEntry(client, systemAdminFullUser); + } + + @Test + public void testCreateServiceIdentityUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1Mock = Mockito.mock(TopLevelDomain.class); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1Mock)).thenReturn(domainMock); + ServiceIdentity serviceMock = Mockito.mock(ServiceIdentity.class); + Mockito.when(c.putServiceIdentity("ServiceAddDom1", "Service1", AUDIT_REF, serviceMock)).thenReturn(serviceMock); + Mockito.when(c.getServiceIdentity("ServiceAddDom1", "Service1")).thenReturn(serviceMock); + Mockito.when(serviceMock.getName()).thenReturn("ServiceAddDom1.Service1".toLowerCase()); + testCreateServiceIdentity(client, systemAdminFullUser); + } + + @Test + public void testCreateEntityUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1Mock = Mockito.mock(TopLevelDomain.class); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1Mock)).thenReturn(domainMock); + Entity entityMock = Mockito.mock(Entity.class); + Mockito.when(c.putEntity("CreateEntityDom1", "Entity1", AUDIT_REF, entityMock)).thenReturn(entityMock); + Mockito.when(c.getEntity("CreateEntityDom1", "Entity1")).thenReturn(entityMock); + Mockito.when(entityMock.getName()).thenReturn("Entity1".toLowerCase()); + testCreateEntity(client, systemAdminFullUser); + } + + @Test + public void testDeleteEntityUserToken() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + TopLevelDomain dom1Mock = Mockito.mock(TopLevelDomain.class); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1Mock)).thenReturn(domainMock); + Entity entityMock = Mockito.mock(Entity.class); + Mockito.when(c.getEntity("DelEntityDom1", "Entity1")).thenReturn(entityMock).thenThrow(new ResourceException(204)); + Mockito.when(c.getEntity("DelEntityDom1", "Entity2")).thenReturn(entityMock,entityMock).thenThrow(new ResourceException(204)); + testDeleteEntity(client, systemAdminFullUser); + } + + @Test + public void testGetPrincipalNull() { + + ZMSClient client = new ZMSClient(getZMSUrl()); + try { + client.getPrincipal(null); + fail(); + } catch (ZMSClientException ex) { + assertTrue(ex.getCode() == 401); + } + client.close(); + } + + @Test + public void testGetPrincipalInvalid() { + + ZMSClient client = new ZMSClient(getZMSUrl()); + try { + client.getPrincipal("abcdefg"); + fail(); + } catch (ZMSClientException ex) { + assertTrue(ex.getCode() == 401); + } + + try { + client.getPrincipal("v=U1;d=coretech;t=12345678;s=signature"); + fail(); + } catch (ZMSClientException ex) { + assertTrue(ex.getCode() == 401); + } + + try { + client.getPrincipal("v=U1;n=storage;t=12345678;s=signature"); + fail(); + } catch (ZMSClientException ex) { + assertTrue(ex.getCode() == 401); + } + client.close(); + } + + Struct setupRespHdrsStruct() { + + Struct respHdrs = new Struct(); + Array values = new Array(); + values.add("Value1A"); + values.add("Value1B"); + respHdrs.put("tag1", values); + + values = new Array(); + values.add("Value2A"); + values.add("Value2B"); + respHdrs.put("tag2", values); + + return respHdrs; + } + + @Test + public void testLookupZMSUrl() { + + System.setProperty(ZMSClient.ZMS_CLIENT_PROP_ATHENZ_CONF, "src/test/resources/athenz.conf"); + ZMSClient client = new ZMSClient(getZMSUrl()); + assertEquals(client.lookupZMSUrl(), "https://server-zms.athenzcompany.com:4443/"); + System.clearProperty(ZMSClient.ZMS_CLIENT_PROP_ATHENZ_CONF); + client.close(); + } + + @Test + public void testLookupZMSUrlInvalidFile() { + System.setProperty(ZMSClient.ZMS_CLIENT_PROP_ATHENZ_CONF, "src/test/resources/athenz_invaild.conf"); + ZMSClient client = new ZMSClient(getZMSUrl()); + assertNull(client.lookupZMSUrl()); + System.clearProperty(ZMSClient.ZMS_CLIENT_PROP_ATHENZ_CONF); + client.close(); + } + + @Test + public void testDeleteDomainTemplate() { + ZMSClient client = createClient(systemAdminUser); + String domName = "templesofold"; + String domName2 = "templesofold2"; + + try { + client.deleteTopLevelDomain(domName, AUDIT_REF); + } catch (ZMSClientException ex) { + // ignore cleanup errors - e.g. not found + } + + TopLevelDomain dom1 = createTopLevelDomainObject(domName, + "Test Domain", "testOrg", systemAdminFullUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + Domain domainMock = Mockito.mock(Domain.class); + Mockito.when(c.postTopLevelDomain(AUDIT_REF, dom1)).thenReturn(domainMock); + client.postTopLevelDomain(AUDIT_REF, dom1); + ServerTemplateList svrTemplListMock = Mockito.mock(ServerTemplateList.class); + @SuppressWarnings("unchecked") + List svrTemplNamesMock = Mockito.mock(List.class); + Mockito.when(c.getServerTemplateList()).thenReturn(svrTemplListMock); + ServerTemplateList svrTemplList = client.getServerTemplateList(); + Mockito.when(svrTemplListMock.getTemplateNames()).thenReturn(svrTemplNamesMock); + List svrTemplNames = svrTemplList.getTemplateNames(); + DomainTemplate domTempl = new DomainTemplate().setTemplateNames(svrTemplNames); + client.putDomainTemplate(domName, AUDIT_REF, domTempl); + DomainTemplateList domTemplListMock = Mockito.mock(DomainTemplateList.class); + Mockito.when(c.getDomainTemplateList(domName)).thenReturn(domTemplListMock); + DomainTemplateList domTemplList = client.getDomainTemplateList(domName); + assertNotNull(domTemplList); + try { + Mockito.when(c.getDomainTemplateList(domName2)).thenThrow(new NullPointerException()) + .thenThrow(new ResourceException(404)); + client.getDomainTemplateList(domName2); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + client.getDomainTemplateList(domName2); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + List templNames = domTemplList.getTemplateNames(); + assertNotNull(templNames); + assertTrue(templNames.size() == svrTemplNames.size()); + // HAVE: domain has all the templates + + // domain has multiple templates: deleting 1 at a time + for (int cnt = 0; cnt < svrTemplNames.size(); ++cnt) { + client.deleteDomainTemplate(domName, svrTemplNames.get(cnt), AUDIT_REF); + domTemplList = client.getDomainTemplateList(domName); + assertNotNull(domTemplList); + templNames = domTemplList.getTemplateNames(); + assertNotNull(templNames); + int templCnt = svrTemplNames.size() - (cnt + 1); + assertTrue(templNames.size() == templCnt, "template should be count=" + templCnt); + for (int cnt2 = cnt + 1; cnt2 < svrTemplNames.size(); ++cnt2) { + assertTrue(templNames.contains(svrTemplNames.get(cnt2)), "should contain=" + svrTemplNames.get(cnt2)); + } + } + + client.deleteTopLevelDomain(domName, AUDIT_REF); + } + + @Test + public void testDeleteDomainTemplateErrorCases() { + ZMSClient client = createClient(systemAdminUser); + ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class); + client.setZMSRDLGeneratedClient(c); + ServerTemplateList svrTemplListMock = Mockito.mock(ServerTemplateList.class); + Mockito.when(c.getServerTemplateList()).thenReturn(svrTemplListMock).thenThrow(new NullPointerException()).thenThrow(new ResourceException(404,"Domain not found")); + ServerTemplateList svrTemplList = client.getServerTemplateList(); + try { + client.getServerTemplateList(); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + try { + client.getServerTemplateList(); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + @SuppressWarnings("unchecked") + List svrTemplNamesMock = Mockito.mock(List.class); + Mockito.when(svrTemplListMock.getTemplateNames()).thenReturn(svrTemplNamesMock); + List svrTemplNames = svrTemplList.getTemplateNames(); + Mockito.when(c.deleteDomainTemplate("nonexistantdomain", svrTemplNames.get(1), AUDIT_REF)).thenThrow(new ResourceException(404, "Domain not found")); + // test: no such domain + try { + client.deleteDomainTemplate("nonexistantdomain", svrTemplNames.get(1), AUDIT_REF); + fail("requesterror not thrown by deleteDomainTemplate"); + } catch (ZMSClientException ex) { + assertEquals(ex.getCode(), 404); + assertTrue(ex.getMessage().contains("Domain not found"), ex.getMessage()); + } + + try { + Mockito.when(c.deleteDomainTemplate("nonexistantdomain2", svrTemplNames.get(1), AUDIT_REF)) + .thenThrow(new NullPointerException()); + client.deleteDomainTemplate("nonexistantdomain2", svrTemplNames.get(1), AUDIT_REF); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + + // test: no such template + String domName = "simontemplar"; + + try { + client.deleteTopLevelDomain(domName, AUDIT_REF); + } catch (ZMSClientException ex) { + // ignore cleanup errors - e.g. not found + } + + TopLevelDomain dom1 = createTopLevelDomainObject(domName, + "Test Domain", "testOrg", systemAdminFullUser); + client.postTopLevelDomain(AUDIT_REF, dom1); + + client.deleteDomainTemplate(domName, svrTemplNames.get(1), AUDIT_REF); + client.deleteTopLevelDomain(domName, AUDIT_REF); + } +} diff --git a/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSLoadData.java b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSLoadData.java new file mode 100644 index 00000000000..9a8ed6ecfb2 --- /dev/null +++ b/clients/java/zms/src/test/java/com/yahoo/athenz/zms/ZMSLoadData.java @@ -0,0 +1,202 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.SimplePrincipal; + +public class ZMSLoadData { + + private static String AUDIT_REF = "zmsjcltloadtest"; + + private Principal createPrincipal(String userName) { + Authority authority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + Principal p = SimplePrincipal.create("user", userName, + "v=U1;d=user;n=" + userName + ";s=signature", 0, authority); + return p; + } + + private ZMSClient getClient(String userName) { + ZMSClient client = new ZMSClient(getZMSUrl()); + client.addCredentials(createPrincipal(userName)); + return client; + } + + private String getZMSUrl() { + + // if we're given a config setting then use that + + String zmsUrl = System.getProperty("yahoo.zms_java_client.zms_url"); + + // if the value is not available then check the env setting + + if (zmsUrl == null) { + zmsUrl = System.getenv("ZMS_URL"); + } + + return zmsUrl; + } + + private TopLevelDomain createTopLevelDomainObject(String name, + String description, String org) { + + TopLevelDomain dom = new TopLevelDomain(); + dom.setName(name); + dom.setDescription(description); + dom.setOrg(org); + dom.setEnabled(true); + + List admins = new ArrayList(); + admins.add("sys.auth.zts"); + admins.add("sys.auth.zpu"); + admins.add("user.zms_admin"); + admins.add("user.user_admin"); + admins.add("user.hga"); + dom.setAdminUsers(admins); + + return dom; + } + + private ServiceIdentity createServiceObject(ZMSClient client, String domainName, + String serviceName, String endPoint, String executable, String user, + String group) { + + ServiceIdentity service = new ServiceIdentity(); + service.setExecutable(executable); + if (group != null) { + service.setGroup(group); + } + if (user!= null) { + service.setUser(user); + } + service.setName(client.generateServiceIdentityName(domainName, serviceName)); + + List pubKeys = new ArrayList<>(); + pubKeys.add(new PublicKeyEntry().setId("0") + .setKey("LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTk" + + "FEQ0JpUUtCZ1FDMXRHU1ZDQTh3bDVldzVZNzZXajJySkFVRApZYW5FSmZLbUFseDVjUS84aEtFVWZTU2dwWHI" + + "zQ3pkaDFhMjZkbGI3bW1LMjlxbVhKWGg2dW1XOUF5ZlRPS1ZvCis2QVNsb1ZVM2F2dnVmbEdVT0VnMmpzbWRh" + + "a1IyNEtjTGpBdTZRclVlNDE3bEczdDhxU1BJR2pTNUMrQ3NKVXcKaDA0aEh4NWYrUEV3eFY0cmJRSURBUUFCC" + + "i0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-")); + + service.setPublicKeys(pubKeys); + + service.setProviderEndpoint(endPoint); + return service; + } + + private Role createRoleObject(ZMSClient client, String domainName, String roleName, + String trust, int memberStart, int memberEnd) { + + Role role = new Role(); + role.setName(client.generateRoleName(domainName, roleName)); + if (trust != null) { + role.setTrust(trust); + } + + List members = new ArrayList(); + if (memberStart != -1) { + for (int i = memberStart; i < memberEnd; i++) { + members.add("user.user" + i); + } + } else { + Random userRandomizer = new Random(); + for (int i = 0; i < 25; i++) { + members.add("user.user" + Math.abs(userRandomizer.nextInt() % 1000)); + } + } + role.setMembers(members); + return role; + } + + private Policy createPolicyObject(ZMSClient client, String domainName, String policyName, + String roleName, String action, String resource, AssertionEffect effect) { + + Policy policy = new Policy(); + policy.setName(client.generatePolicyName(domainName, policyName)); + + Assertion assertion = new Assertion(); + assertion.setAction(action); + assertion.setEffect(effect); + assertion.setResource(resource); + assertion.setRole(client.generateRoleName(domainName, roleName)); + + List assertList = new ArrayList(); + assertList.add(assertion); + + policy.setAssertions(assertList); + return policy; + } + + public static void main(String [] args) { + + if (args.length != 3) { + System.out.println("ZMSLoadData "); + System.exit(1); + } + + String admin = args[0]; + int startIndex = Integer.parseInt(args[1]); + int endIndex = Integer.parseInt(args[2]); + ZMSLoadData data = new ZMSLoadData(); + + for (int i = startIndex; i < endIndex; i++) { + + ZMSClient client = data.getClient(admin); + + String domainName = "TestDomain" + i; + TopLevelDomain domain = data.createTopLevelDomainObject(domainName, "Test Domain: " + domainName, "CoreTech"); + client.postTopLevelDomain(AUDIT_REF, domain); + + for (int j = 0; j < 10; j++) { + + String serviceName = "Service" + j; + ServiceIdentity service = data.createServiceObject(client, domainName, serviceName, + "http://localhost:9080/", "/usr/bin/java", null, null); + + client.putServiceIdentity(domainName, serviceName, AUDIT_REF, service); + } + + for (int j = 0; j < 40; j++) { + + String roleName = "Role" + j; + Role role = data.createRoleObject(client, domainName, roleName, null, j * 25, (j + 1) * 25); + client.putRole(domainName, roleName, AUDIT_REF, role); + } + + for (int j = 40; j < 100; j++) { + + String roleName = "Role" + j; + Role role = data.createRoleObject(client, domainName, roleName, null, -1, 1000); + client.putRole(domainName, roleName, AUDIT_REF, role); + } + + for (int j = 0; j < 100; j++) { + + String policyName = "Policy" + j; + String roleName = "Role" + j; + Policy policy = data.createPolicyObject(client, domainName, policyName, roleName, + "*", "*", AssertionEffect.ALLOW); + client.putPolicy(domainName, policyName, AUDIT_REF, policy); + } + } + } +} diff --git a/clients/java/zms/src/test/resources/athenz.conf b/clients/java/zms/src/test/resources/athenz.conf new file mode 100644 index 00000000000..d6983863df3 --- /dev/null +++ b/clients/java/zms/src/test/resources/athenz.conf @@ -0,0 +1,16 @@ +{ + "zmsUrl": "https://server-zms.athenzcompany.com:4443/", + "ztsUrl": "https://server-zts.athenzcompany.com:4443/", + "ztsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + } + ], + "zmsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + } + ] +} diff --git a/clients/java/zms/src/test/resources/logback.xml b/clients/java/zms/src/test/resources/logback.xml new file mode 100644 index 00000000000..c4a0357b8d5 --- /dev/null +++ b/clients/java/zms/src/test/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + System.out + + %-4relative [%thread] %-5level %class - %msg%n + + + + + + + + + + diff --git a/clients/java/zpe/README.md b/clients/java/zpe/README.md new file mode 100644 index 00000000000..03f6da37d2e --- /dev/null +++ b/clients/java/zpe/README.md @@ -0,0 +1,71 @@ +# zpe_java_client + +AuthNG ZPE client lib to perform data plane authorization for client requests + +## Contents + +* [Summary](#summary) +* [Details](#details) + + +## Summary + +This is the ZPE(AuthZ Policy Engine) front-end API to perform client +access authorization to resources. + +The implementation is thread safe. + +## Details + +This library will be used by service components to check client access +authorization to resources supplied by the service component. + +The library will read the authorization policies from the file system. +These policy files will be in JSON format as returned by the ZMS REST API: +getSignedPolicies(). +The directory containing these policy files is by default /home/athenz/var/zpe +but will be configurable. + +These authorization policies can be for several domains. + +ZPE will monitor the directory for updates to the files or new files added. +As new files are deposited to the policy file directory, ZPE will read +in the new policies to replace the old ones or add them for a new domain. +Each policy file will be validated against its signature to ensure +validity of the policy data. + +It is expected that each file contains all the policies for a domain. + +The policy files can be deposited there in several ways: +1. Athenz Policy Updater +2. Manually deposited + + +System properties: + + athenz.zpe.policy_dir + Default value: ROOT + /var/zpe + Should contain a valid directory path. + + athenz.zpe.monitor_timeout_secs + Default value: 300 + This time interval is used to check for changes to the policy files. + + athenz.zpe.cleanup_tokens_secs + Default value: 600 (10 minutes) + This time interval is used to check for expired tokens in the cache. + It is dependent on the athenz.zpe.monitor_timeout_secs. + For intervals less than monitor_timeout_secs, the enforcement check + will take place every monitor_timeout_secs seconds. + For intervals greater than monitor_timeout_secs, enforcement checks (ec) + will take place every: + ec * monitor_timeout_secs >= cleanup_tokens_secs + where ec is the smallest integer such that ec * monitor_timeout_secs >= cleanup_tokens_secs + Ex: monitor_timeout_secs=300, cleanup_tokens_secs=500, ec = 600 seconds + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() + diff --git a/clients/java/zpe/pom.xml b/clients/java/zpe/pom.xml new file mode 100644 index 00000000000..b58824720f5 --- /dev/null +++ b/clients/java/zpe/pom.xml @@ -0,0 +1,205 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../../pom.xml + + + zpe_java_client + jar + zpe_java_client + ZPE Client Library (Java) + + + 1.9.1 + benchmarks + + + + + ${project.groupId} + zts_core + ${project.parent.version} + + + ${project.groupId} + auth_core + ${project.parent.version} + + + ${project.groupId} + client_common + ${project.parent.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + prepare-package + + jar + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + + + + + + com.fasterxml.jackson.core + + + + + com.fasterxml.jackson + athenz.shade.zpe.com.fasterxml.jackson + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + + default-test + + test + + + alphabetical + + 1 + 1 + src/test/resources/pol_dir + src/test/resources/athenz.conf + src/test/resources/logback.xml + + + + + + + + + + + + + jmh-performance-test + + + src/main/java/com/yahoo/athenz/zpe/Performance.java + + + + + org.openjdk.jmh + jmh-core + ${jmh.version} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + ${uberjar.name} + + + org.openjdk.jmh.Main + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + + + diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/AuthZpeClient.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/AuthZpeClient.java new file mode 100644 index 00000000000..349825f5a08 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/AuthZpeClient.java @@ -0,0 +1,797 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import com.yahoo.athenz.auth.impl.RoleAuthority; +import com.yahoo.athenz.auth.token.RoleToken; +import com.yahoo.athenz.zpe.match.ZpeMatch; +import com.yahoo.athenz.zpe.pkey.PublicKeyStore; +import com.yahoo.athenz.zpe.pkey.PublicKeyStoreFactory; +import com.yahoo.rdl.Struct; + +import java.security.PublicKey; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AuthZpeClient { + + private static final Logger LOG = LoggerFactory.getLogger(AuthZpeClient.class); + + public static final String ZPE_UPDATER_CLASS = "com.yahoo.athenz.zpe.ZpeUpdater"; + public static final String ZPE_PKEY_CLASS = "com.yahoo.athenz.zpe.pkey.file.FilePublicKeyStoreFactory"; + + public static final String ZPE_TOKEN_HDR = System.getProperty(RoleAuthority.ATHENZ_PROP_ROLE_HEADER, RoleAuthority.HTTP_HEADER);; + + public static final String ZTS_PUBLIC_KEY = "zts_public_key"; + public static final String ZMS_PUBLIC_KEY = "zms_public_key"; + + public static final String ZTS_PUBLIC_KEY_PREFIX = "zts.public_key."; + public static final String ZMS_PUBLIC_KEY_PREFIX = "zms.public_key."; + + public static final String SYS_AUTH_DOMAIN = "sys.auth"; + public static final String ZTS_SERVICE_NAME = "zts"; + public static final String ZMS_SERVICE_NAME = "zms"; + + public static final String DEFAULT_DOMAIN = "sys.auth"; + public static final String UNKNOWN_DOMAIN = "unknown"; + + public static ZpeMetric zpeMetric = new ZpeMetric(); + + private static String zpeClientImplName; + private static int allowedOffset = 300; + + private static ZpeClient zpeClt = null; + private static PublicKeyStore publicKeyStore = null; + + public enum AccessCheckStatus { + ALLOW { + public String toString() { + return "Access Check was explicitly allowed"; + } + }, + DENY { + public String toString() { + return "Access Check was explicitly denied"; + } + }, + DENY_NO_MATCH { + public String toString() { + return "Access denied due to no match to any of the assertions defined in domain policy file"; + } + }, + DENY_ROLETOKEN_EXPIRED { + public String toString() { + return "Access denied due to expired RoleToken"; + } + }, + DENY_ROLETOKEN_INVALID { + public String toString() { + return "Access denied due to invalid RoleToken"; + } + }, + DENY_DOMAIN_MISMATCH { + public String toString() { + return "Access denied due to domain mismatch between Resource and RoleToken"; + } + }, + DENY_DOMAIN_NOT_FOUND { + public String toString() { + return "Access denied due to domain not found in library cache"; + } + }, + DENY_DOMAIN_EXPIRED { + public String toString() { + return "Access denied due to expired domain policy file"; + } + }, + DENY_DOMAIN_EMPTY { + public String toString() { + return "Access denied due to no policies in the domain file"; + } + }, + DENY_INVALID_PARAMETERS { + public String toString() { + return "Access denied due to invalid/empty action/resource values"; + }; + } + } + + static { + + // instantiate implementation classes + + zpeClientImplName = System.getProperty(ZpeConsts.ZPE_PROP_CLIENT_IMPL, ZPE_UPDATER_CLASS); + try { + zpeClt = getZpeClient(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) { + LOG.error("Unable to instantiate zpe class: " + zpeClientImplName, ex); + throw new RuntimeException(ex); + } + zpeClt.init(null); + + allowedOffset = Integer.parseInt(System.getProperty(ZpeConsts.ZPE_PROP_TOKEN_OFFSET, "300")); + + // case of invalid value, we'll default back to 5 minutes + + if (allowedOffset < 0) { + allowedOffset = 300; + } + + String pkeyFactoryClass = System.getProperty(ZpeConsts.ZPE_PROP_PUBLIC_KEY_CLASS, ZPE_PKEY_CLASS); + + PublicKeyStoreFactory publicKeyStoreFactory = null; + try { + publicKeyStoreFactory = (PublicKeyStoreFactory) Class.forName(pkeyFactoryClass).newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) { + LOG.error("Invalid PublicKeyStore class: " + pkeyFactoryClass, ex); + throw new RuntimeException(ex); + } + publicKeyStore = publicKeyStoreFactory.create(); + } + + public static void init() { + if (LOG.isDebugEnabled()) { + LOG.debug("Init: load the ZPE"); + } + } + + public static PublicKey getZtsPublicKey(String keyId) { + return publicKeyStore.getZtsKey(keyId); + } + + public static PublicKey getZmsPublicKey(String keyId) { + return publicKeyStore.getZmsKey(keyId); + } + + /** + * Determine if access(action) is allowed against the specified resource by + * a user represented by the user (cltToken, cltTokenName). + * @param roleToken - value for the REST header: Athenz-Role-Auth + * ex: "v=Z1;d=angler;r=admin;a=aAkjbbDMhnLX;t=1431974053;e=1431974153;k=0" + * @param angResource is a domain qualified resource the calling service + * will check access for. ex: my_domain:my_resource + * ex: "angler:pondsKernCounty" + * ex: "sports:service.storage.tenant.Activator.ActionMap" + * @param action is the type of access attempted by a client + * ex: "read" + * ex: "scan" + * @return AccessCheckStatus if the user can access the resource via the specified action + * the result is ALLOW otherwise one of the DENY_* values specifies the exact + * reason why the access was denied + */ + public static AccessCheckStatus allowAccess(String roleToken, String angResource, String action) { + StringBuilder matchRoleName = new StringBuilder(256); + return allowAccess(roleToken, angResource, action, matchRoleName); + } + + /** + * Determine if access(action) is allowed against the specified resource by + * a user represented by the user (cltToken, cltTokenName). + * @param roleToken - value for the REST header: Athenz-Role-Auth + * ex: "v=Z1;d=angler;r=admin;a=aAkjbbDMhnLX;t=1431974053;e=1431974153;k=0" + * @param angResource is a domain qualified resource the calling service + * will check access for. ex: my_domain:my_resource + * ex: "angler:pondsKernCounty" + * ex: "sports:service.storage.tenant.Activator.ActionMap" + * @param action is the type of access attempted by a client + * ex: "read" + * ex: "scan" + * @param matchRoleName - [out] will include the role name that the result was based on + * it will be not be set if the failure is due to expired/invalid tokens or + * there were no matches thus a default value of DENY_NO_MATCH is returned + * @return AccessCheckStatus if the user can access the resource via the specified action + * the result is ALLOW otherwise one of the DENY_* values specifies the exact + * reason why the access was denied + */ + public static AccessCheckStatus allowAccess(String roleToken, String angResource, String action, + StringBuilder matchRoleName) { + + if (LOG.isDebugEnabled()) { + LOG.debug("allowAccess: action=" + action + " resource=" + angResource); + } + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME, DEFAULT_DOMAIN); + + RoleToken rToken = null; + Map tokenCache = null; + try { + ZpeClient zpeclt = getZpeClient(); + tokenCache = zpeclt.getRoleTokenCacheMap(); + rToken = tokenCache.get(roleToken); + } catch (Exception exc) { + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_CACHE_FAILURE, DEFAULT_DOMAIN); + LOG.error("allowAccess: token cache failure, exc: ", exc); + } + + if (rToken == null) { + + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_CACHE_NOT_FOUND, DEFAULT_DOMAIN); + rToken = new RoleToken(roleToken); + + // validate the token + if (rToken.validate(getZtsPublicKey(rToken.getKeyId()), allowedOffset, null) == false) { + LOG.error("allowAccess: Authorization denied. Authentication of token failed for token=" + + rToken.getSignedToken()); + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_INVALID_TOKEN, rToken.getDomain()); + return AccessCheckStatus.DENY_ROLETOKEN_INVALID; + } + + if (tokenCache != null) { + tokenCache.put(roleToken, rToken); + } + } else { + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_CACHE_SUCCESS, rToken.getDomain()); + } + + AccessCheckStatus status = allowAccess(rToken, angResource, action, matchRoleName); + return status; + } + + /** + * Determine if access(action) is allowed against the specified resource by + * a user represented by the RoleToken. + * @param rToken represents the role token sent by the client that wants access to the resource + * @param angResource is a domain qualified resource the calling service + * will check access for. ex: my_domain:my_resource + * ex: "angler:pondsKernCounty" + * ex: "sports:service.storage.tenant.Activator.ActionMap" + * @param action is the type of access attempted by a client + * ex: "read" + * ex: "scan" + * @param matchRoleName - [out] will include the role name that the result was based on + * it will be not be set if the failure is due to expired/invalid tokens or + * there were no matches thus a default value of DENY_NO_MATCH is returned + * @return AccessCheckStatus if the user can access the resource via the specified action + * the result is ALLOW otherwise one of the DENY_* values specifies the exact + * reason why the access was denied + **/ + public static AccessCheckStatus allowAccess(RoleToken rToken, String angResource, String action, + StringBuilder matchRoleName) { + + // check the token expiration + if (rToken == null) { + LOG.error("allowAccess: Authorization denied. Token is null"); + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_INVALID_TOKEN, UNKNOWN_DOMAIN); + return AccessCheckStatus.DENY_ROLETOKEN_INVALID; + } + long now = System.currentTimeMillis() / 1000; + long expiry = rToken.getExpiryTime(); + if (expiry != 0 && expiry < now) { + String signedToken = rToken.getSignedToken(); + LOG.error("allowAccess: Authorization denied. Token expired. now=" + + now + " expiry=" + expiry + " token=" + signedToken); + Map tokenCache = null; + try { + ZpeClient zpeclt = getZpeClient(); + tokenCache = zpeclt.getRoleTokenCacheMap(); + tokenCache.remove(signedToken); + } catch (Exception exc) { + LOG.error("allowAccess: token cache failure, exc: ", exc); + } + + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_EXPIRED_TOKEN, rToken.getDomain()); + return AccessCheckStatus.DENY_ROLETOKEN_EXPIRED; + } + + String tokenDomain = rToken.getDomain(); // ZToken contains the domain + List roles = rToken.getRoles(); // ZToken contains roles + + if (LOG.isDebugEnabled()) { + if (roles != null) { + for (String role: roles) { + LOG.debug("allowAccess: token role=" + role); + } + } + } + + return allowActionZPE(action, tokenDomain, angResource, roles, matchRoleName); + } + + /** + * Determine if access(action) is allowed against the specified resource by + * a user represented by the list of role tokens. + * @param roleTokenList - values from the REST header(s): Athenz-Role-Auth + * ex: "v=Z1;d=angler;r=admin;a=aAkjbbDMhnLX;t=1431974053;e=1431974153;k=0" + * @param angResource is a domain qualified resource the calling service + * will check access for. ex: my_domain:my_resource + * ex: "angler:pondsKernCounty" + * ex: "sports:service.storage.tenant.Activator.ActionMap" + * @param action is the type of access attempted by a client + * ex: "read" + * ex: "scan" + * @param matchRoleName - [out] will include the role name that the result was based on + * it will be not be set if the failure is due to expired/invalid tokens or + * there were no matches thus a default value of DENY_NO_MATCH is returned + * @return AccessCheckStatus if the user can access the resource via the specified action + * the result is ALLOW otherwise one of the DENY_* values specifies the exact + * reason why the access was denied + */ + public static AccessCheckStatus allowAccess(List roleTokenList, + String angResource, String action, StringBuilder matchRoleName) { + + AccessCheckStatus retStatus = AccessCheckStatus.DENY_NO_MATCH; + StringBuilder roleName = null; + for (String roleToken: roleTokenList) { + StringBuilder rName = new StringBuilder(64); + AccessCheckStatus status = allowAccess(roleToken, angResource, action, rName); + if (status == AccessCheckStatus.DENY) { + matchRoleName.append(rName); + return status; + } else if (retStatus != AccessCheckStatus.ALLOW) { // only DENY over-rides ALLOW + retStatus = status; + roleName = rName; + } + } + + if (roleName != null) { + matchRoleName.append(roleName.toString()); + } + + return retStatus; + } + + /** + * Validate the RoleToken and return the parsed token object that + * could be used to extract all fields from the role token. If the role + * token is invalid, then null object is returned. + * @param roleToken - value for the REST header: Athenz-Role-Auth + * ex: "v=Z1;d=angler;r=admin;a=aAkjbbDMhnLX;t=1431974053;e=1431974153;k=0" + * @return RoleToken if the token is validated successfully otherwise null + */ + public static RoleToken validateRoleToken(String roleToken) { + + RoleToken rToken = null; + + // first check in our cache in case we have already seen and successfully + // validated this role token (signature validation is expensive) + + Map tokenCache = null; + try { + ZpeClient zpeclt = getZpeClient(); + tokenCache = zpeclt.getRoleTokenCacheMap(); + rToken = tokenCache.get(roleToken); + } catch (Exception exc) { + } + + // if the token is not in the cache then we need to + // validate the token now + + if (rToken == null) { + rToken = new RoleToken(roleToken); + + // validate the token + + if (rToken.validate(getZtsPublicKey(rToken.getKeyId()), allowedOffset, null) == false) { + return null; + } + + if (tokenCache != null) { + tokenCache.put(roleToken, rToken); + } + } + + return rToken; + } + + static ZpeClient getZpeClient() throws InstantiationException, IllegalAccessException, ClassNotFoundException { + + if (zpeClt != null) { + return zpeClt; + } + + return (ZpeClient) Class.forName(zpeClientImplName).newInstance(); + } + + /* + * Peel off domain name from the assertion string if it matches + * domain and return the string without the domain prefix. + * Else, return default value + */ + static String stripDomainPrefix(String assertString, String domain, String defaultValue) { + int index = assertString.indexOf(':'); + if (index == -1) { + return assertString; + } + + if (assertString.substring(0, index).equals(domain) == false) { + return defaultValue; + } + + return assertString.substring(index + 1); + } + + static boolean isRegexMetaCharacter(char regexChar) { + switch (regexChar) { + case '^': + case '$': + case '.': + case '|': + case '[': + case '+': + case '\\': + case '(': + case ')': + case '{': + return true; + default: + return false; + } + } + + public static String patternFromGlob(String glob) { + StringBuilder sb = new StringBuilder("^"); + int len = glob.length(); + for (int i = 0; i < len; i++) { + char c = glob.charAt(i); + if (c == '*') { + sb.append(".*"); + } else if (c == '?') { + sb.append('.'); + } else { + if (isRegexMetaCharacter(c)) { + sb.append('\\'); + } + sb.append(c); + } + } + sb.append("$"); + return sb.toString(); + } + + // check action access in the domain to the resource with the given roles + // + static AccessCheckStatus allowActionZPE(String action, String tokenDomain, String angResource, + List roles, StringBuilder matchRoleName) { + + StringBuilder sb = new StringBuilder("allowActionZPE: domain("); + sb.append(tokenDomain).append(") action(").append(action). + append(") resource(").append(angResource).append(")"); + String msgPrefix = sb.toString(); + + if (LOG.isDebugEnabled()) { + LOG.debug(msgPrefix + " STARTING"); + } + + if (roles == null || roles.size() == 0) { + LOG.error(msgPrefix + " ERROR: No roles so access denied"); + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_INVALID_TOKEN, tokenDomain); + return AccessCheckStatus.DENY_ROLETOKEN_INVALID; + } + + if (tokenDomain == null || tokenDomain.isEmpty() == true) { + LOG.error(msgPrefix + " ERROR: No domain so access denied"); + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_INVALID_TOKEN, DEFAULT_DOMAIN); + return AccessCheckStatus.DENY_ROLETOKEN_INVALID; + } + + if (action == null || action.isEmpty() == true) { + LOG.error(msgPrefix + " ERROR: No action so access denied"); + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_ERROR, tokenDomain); + return AccessCheckStatus.DENY_INVALID_PARAMETERS; + } + action = action.toLowerCase(); + + if (angResource == null || angResource.isEmpty() == true) { + LOG.error(msgPrefix + " ERROR: No resource so access denied"); + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_ERROR, tokenDomain); + return AccessCheckStatus.DENY_INVALID_PARAMETERS; + } + angResource = angResource.toLowerCase(); + angResource = stripDomainPrefix(angResource, tokenDomain, null); + + // Note: if domain in token doesn't match domain in resource then there + // will be no match of any resource in the assertions - so deny immediately + + if (angResource == null) { + StringBuilder sbErr = new StringBuilder(512); + sbErr.append(msgPrefix).append(" ERROR: Domain mismatch in token("). + append(tokenDomain).append(") and resource so access denied"); + LOG.error(sbErr.toString()); + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_DOMAIN_MISMATCH, tokenDomain); + return AccessCheckStatus.DENY_DOMAIN_MISMATCH; + } + + // first hunt by role for deny assertions since deny takes precedence + // over allow assertions + + AccessCheckStatus status = AccessCheckStatus.DENY_DOMAIN_NOT_FOUND; + Map> roleMap = getRoleSpecificDenyPolicies(tokenDomain); + if (roleMap != null && !roleMap.isEmpty()) { + if (actionByRole(action, tokenDomain, angResource, roles, roleMap, matchRoleName)) { + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_DENY, tokenDomain); + return AccessCheckStatus.DENY; + } else { + status = AccessCheckStatus.DENY_NO_MATCH; + } + } else if (roleMap != null) { + status = AccessCheckStatus.DENY_DOMAIN_EMPTY; + } + + // if the check was not explicitly denied by a standard role, then + // let's process our wildcard roles for deny assertions + + roleMap = getWildCardDenyPolicies(tokenDomain); + if (roleMap != null && !roleMap.isEmpty()) { + if (actionByWildCardRole(action, tokenDomain, angResource, roles, roleMap, matchRoleName)) { + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_DENY, tokenDomain); + return AccessCheckStatus.DENY; + } else { + status = AccessCheckStatus.DENY_NO_MATCH; + } + } else if (status != AccessCheckStatus.DENY_NO_MATCH && roleMap != null) { + status = AccessCheckStatus.DENY_DOMAIN_EMPTY; + } + + // so far it did not match any deny assertions so now let's + // process our allow assertions + + roleMap = getRoleSpecificAllowPolicies(tokenDomain); + if (roleMap != null && !roleMap.isEmpty()) { + if (actionByRole(action, tokenDomain, angResource, roles, roleMap, matchRoleName)) { + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_ALLOW, tokenDomain); + return AccessCheckStatus.ALLOW; + } else { + status = AccessCheckStatus.DENY_NO_MATCH; + } + } else if (status != AccessCheckStatus.DENY_NO_MATCH && roleMap != null) { + status = AccessCheckStatus.DENY_DOMAIN_EMPTY; + } + + // at this point we either got an allow or didn't match anything so we're + // going to try the wildcard roles + + roleMap = getWildCardAllowPolicies(tokenDomain); + if (roleMap != null && !roleMap.isEmpty()) { + if (actionByWildCardRole(action, tokenDomain, angResource, roles, roleMap, matchRoleName)) { + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_ALLOW, tokenDomain); + return AccessCheckStatus.ALLOW; + } else { + status = AccessCheckStatus.DENY_NO_MATCH; + } + } else if (status != AccessCheckStatus.DENY_NO_MATCH && roleMap != null) { + status = AccessCheckStatus.DENY_DOMAIN_EMPTY; + } + + if (status == AccessCheckStatus.DENY_DOMAIN_NOT_FOUND) { + if (LOG.isDebugEnabled()) { + LOG.debug(msgPrefix + ": No role map found for domain=" + tokenDomain + + " so access denied"); + } + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_DOMAIN_NOT_FOUND, tokenDomain); + } else if (status == AccessCheckStatus.DENY_DOMAIN_EMPTY) { + if (LOG.isDebugEnabled()) { + LOG.debug(msgPrefix + ": No policy assertions for domain=" + tokenDomain + + " so access denied"); + } + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_DOMAIN_EMPTY, tokenDomain); + } else { + zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME_DENY_NO_MATCH, tokenDomain); + } + + return status; + } + + static boolean matchAssertions(List asserts, String role, String action, + String resource, StringBuilder matchRoleName, String msgPrefix) { + + ZpeMatch matchStruct = null; + String passertAction = null; + String passertResource = null; + String passertRole = null; + String polName = null; + + for (Struct strAssert: asserts) { + + if (LOG.isDebugEnabled()) { + + // this strings are only used for debug statements so we'll + // only retrieve them if debug option is enabled + + passertAction = strAssert.getString(ZpeConsts.ZPE_FIELD_ACTION); + passertResource = strAssert.getString(ZpeConsts.ZPE_FIELD_RESOURCE); + passertRole = strAssert.getString(ZpeConsts.ZPE_FIELD_ROLE); + polName = strAssert.getString(ZpeConsts.ZPE_FIELD_POLICY_NAME); + + LOG.debug(msgPrefix + ": Process Assertion: policy(" + polName + + ") assert-action=" + passertAction + + " assert-resource=" + passertResource + " assert-role=" + passertRole); + } + + // ex: "mod* + + matchStruct = (ZpeMatch) strAssert.get(ZpeConsts.ZPE_ACTION_MATCH_STRUCT); + if (!matchStruct.matches(action)) { + if (LOG.isDebugEnabled()) { + LOG.debug(msgPrefix + ": policy(" + polName + ") regexpr-match: FAILed: assert-action(" + + passertAction + ") doesn't match action(" + action + ")"); + } + continue; + } + + // ex: "weather:service.storage.tenant.sports.*" + matchStruct = (ZpeMatch) strAssert.get(ZpeConsts.ZPE_RESOURCE_MATCH_STRUCT); + if (!matchStruct.matches(resource)) { + if (LOG.isDebugEnabled()) { + LOG.debug(msgPrefix + ": policy(" + polName + ") regexpr-match: FAILed: assert-resource(" + + passertResource + ") doesn't match resource(" + resource + ")"); + } + continue; + } + + // update the match role name + + matchRoleName.setLength(0); + matchRoleName.append(role); + + return true; + } + + return false; + } + + static boolean actionByRole(String action, String domain, String angResource, + List roles, Map> roleMap, StringBuilder matchRoleName) { + + // msgPrefix is only used in our debug statements so we're only + // going to generate the value if debug is enabled + + String msgPrefix = null; + if (LOG.isDebugEnabled()) { + StringBuilder sb = new StringBuilder("allowActionByRole: domain("); + sb.append(domain).append(") action(").append(action). + append(") resource(").append(angResource).append(")"); + msgPrefix = sb.toString(); + } + + for (String role : roles) { + if (LOG.isDebugEnabled()) { + LOG.debug(msgPrefix + ": Process role (" + role + ")"); + } + + List asserts = roleMap.get(role); + if (asserts == null || asserts.isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug(msgPrefix + ": No policy assertions in domain=" + domain + + " for role=" + role + " so access denied"); + } + continue; + } + + // see if any of its assertions match the action and resource + // the assert action value does not have the domain prefix + // ex: "Modify" + // the assert resource value has the domain prefix + // ex: "angler:angler.stuff" + + if (matchAssertions(asserts, role, action, angResource, matchRoleName, msgPrefix)) { + return true; + } + } + + return false; + } + + static boolean actionByWildCardRole(String action, String domain, String angResource, + List roles, Map> roleMap, StringBuilder matchRoleName) { + + String msgPrefix = null; + if (LOG.isDebugEnabled()) { + StringBuilder sb = new StringBuilder("allowActionByWildCardRole: domain("); + sb.append(domain).append(") action(").append(action). + append(") resource(").append(angResource).append(")"); + msgPrefix = sb.toString(); + } + + // find policy matching resource and action + // get assertions for given domain+role + // then cycle thru those assertions looking for matching action and resource + + // we will visit each of the wildcard roles + // + Set keys = roleMap.keySet(); + + for (String role: roles) { + + if (LOG.isDebugEnabled()) { + LOG.debug(msgPrefix + ": Process role (" + role + ")"); + } + + for (String roleName : keys) { + List asserts = roleMap.get(roleName); + if (asserts == null || asserts.isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug(msgPrefix + ": No policy assertions in domain=" + domain + + " for role=" + role + " so access denied"); + } + continue; + } + + Struct structAssert = asserts.get(0); + ZpeMatch matchStruct = (ZpeMatch) structAssert.get(ZpeConsts.ZPE_ROLE_MATCH_STRUCT); + if (!matchStruct.matches(role)) { + if (LOG.isDebugEnabled()) { + String polName = structAssert.getString(ZpeConsts.ZPE_FIELD_POLICY_NAME); + LOG.debug(msgPrefix + ": policy(" + polName + + ") regexpr-match: FAILed: assert-role(" + roleName + + ") doesnt match role(" + role + ")"); + } + continue; + } + + // HAVE: matched the role with the wildcard + + // see if any of its assertions match the action and resource + // the assert action value does not have the domain prefix + // ex: "Modify" + // the assert resource value has the domain prefix + // ex: "angler:angler.stuff" + + if (matchAssertions(asserts, roleName, action, angResource, matchRoleName, msgPrefix)) { + return true; + } + } + } + + return false; + } + + static Map> getWildCardAllowPolicies(String domain) { + try { + ZpeClient zpeclt = getZpeClient(); + Map> roleAsserts = zpeclt.getWildcardAllowAssertions(domain); + return roleAsserts; + } catch (Exception exc) { + LOG.error("getWildCardAllowPolicies: exc: ", exc); + } + return null; + } + + static Map> getRoleSpecificAllowPolicies(String domain) { + try { + ZpeClient zpeclt = getZpeClient(); + Map> roleAsserts = zpeclt.getRoleAllowAssertions(domain); + return roleAsserts; + } catch (Exception exc) { + LOG.error("getRoleSpecificAllowPolicies: exc: ", exc); + } + return null; + } + + static Map> getWildCardDenyPolicies(String domain) { + try { + ZpeClient zpeclt = getZpeClient(); + Map> roleAsserts = zpeclt.getWildcardDenyAssertions(domain); + return roleAsserts; + } catch (Exception exc) { + LOG.error("getWildCardDenyPolicies: exc: ", exc); + } + return null; + } + + static Map> getRoleSpecificDenyPolicies(String domain) { + try { + ZpeClient zpeclt = getZpeClient(); + Map> roleAsserts = zpeclt.getRoleDenyAssertions(domain); + return roleAsserts; + } catch (Exception exc) { + LOG.error("getRoleSpecificDenyPolicies: exc: ", exc); + } + return null; + } +} + diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeClient.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeClient.java new file mode 100644 index 00000000000..a869bab4213 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeClient.java @@ -0,0 +1,49 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import java.util.List; +import java.util.Map; + +import com.yahoo.athenz.auth.token.RoleToken; +import com.yahoo.rdl.Struct; + + +public interface ZpeClient { + + // @param domain can be null + public void init(String domain); + + // return current cache of role tokens + public Map getRoleTokenCacheMap(); + + // return the role assertion map for the specified domain with allow effect + // key is role name, value is List of assertions for that role + public Map> getRoleAllowAssertions(String domain); + + // return the wildcard role assertion map for the specified domain with allow effect + // key is role name, value is List of assertions for that role + public Map> getWildcardAllowAssertions(String domain); + + // return the role assertion map for the specified domain with deny effect + // key is role name, value is List of assertions for that role + public Map> getRoleDenyAssertions(String domain); + + // return the wildcard role assertion map for the specified domain with deny effect + // key is role name, value is List of assertions for that role + public Map> getWildcardDenyAssertions(String domain); +} + diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeConsts.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeConsts.java new file mode 100644 index 00000000000..6f962357ba5 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeConsts.java @@ -0,0 +1,68 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import com.yahoo.athenz.zts.DomainMetricType; + +public final class ZpeConsts { + + public static final String ZPE_ACTION_MATCH_STRUCT = "actionMatchStruct"; + public static final String ZPE_RESOURCE_MATCH_STRUCT = "resourceMatchStruct"; + public static final String ZPE_ROLE_MATCH_STRUCT = "roleMatchStruct"; + + public static final String ZPE_FIELD_ACTION = "action"; + public static final String ZPE_FIELD_RESOURCE = "resource"; + public static final String ZPE_FIELD_ROLE = "role"; + public static final String ZPE_FIELD_EFFECT = "effect"; + public static final String ZPE_FIELD_POLICY_NAME = "polname"; + + public static final String ZPE_METRIC_SCOREBOARD_NAME = "athenz_zpe_java_client"; + public static final String ZPE_METRIC_NAME = DomainMetricType.ACCESS_ALLOWED.toString(); + public static final String ZPE_METRIC_NAME_DENY = DomainMetricType.ACCESS_ALLOWED_DENY.toString(); + public static final String ZPE_METRIC_NAME_DENY_NO_MATCH = DomainMetricType.ACCESS_ALLOWED_DENY_NO_MATCH.toString(); + public static final String ZPE_METRIC_NAME_ALLOW = DomainMetricType.ACCESS_ALLOWED_ALLOW.toString(); + public static final String ZPE_METRIC_NAME_ERROR = DomainMetricType.ACCESS_ALLOWED_ERROR.toString(); + public static final String ZPE_METRIC_NAME_INVALID_TOKEN = DomainMetricType.ACCESS_ALLOWED_TOKEN_INVALID.toString(); + public static final String ZPE_METRIC_NAME_EXPIRED_TOKEN = DomainMetricType.ACCESS_Allowed_TOKEN_EXPIRED.toString(); + public static final String ZPE_METRIC_NAME_DOMAIN_NOT_FOUND = DomainMetricType.ACCESS_ALLOWED_DOMAIN_NOT_FOUND.toString(); + public static final String ZPE_METRIC_NAME_DOMAIN_MISMATCH = DomainMetricType.ACCESS_ALLOWED_DOMAIN_MISMATCH.toString(); + public static final String ZPE_METRIC_NAME_DOMAIN_EXPIRED = DomainMetricType.ACCESS_ALLOWED_DOMAIN_EXPIRED.toString(); + public static final String ZPE_METRIC_NAME_DOMAIN_EMPTY = DomainMetricType.ACCESS_ALLOWED_DOMAIN_EMPTY.toString(); + public static final String ZPE_METRIC_NAME_CACHE_FAILURE = DomainMetricType.ACCESS_ALLOWED_TOKEN_CACHE_FAILURE.toString(); + public static final String ZPE_METRIC_NAME_CACHE_NOT_FOUND = DomainMetricType.ACCESS_ALLOWED_TOKEN_CACHE_NOT_FOUND.toString(); + public static final String ZPE_METRIC_NAME_CACHE_SUCCESS = DomainMetricType.ACCESS_ALLOWED_TOKEN_CACHE_SUCCESS.toString(); + public static final String ZPE_METRIC_NAME_TOKEN_VALIDATE = DomainMetricType.ACCESS_ALLOWED_TOKEN_VALIDATE.toString(); + public static final String ZPE_METRIC_LOAD_FILE_FAIL = DomainMetricType.LOAD_FILE_FAIL.toString(); + public static final String ZPE_METRIC_LOAD_FILE_GOOD = DomainMetricType.LOAD_FILE_GOOD.toString(); + public static final String ZPE_METRIC_LOAD_DOM_GOOD = DomainMetricType.LOAD_DOMAIN_GOOD.toString(); + + // properties + public static final String ZPE_PROP_ATHENZ_CONF = "athenz.athenz_conf"; + + public static final String ZPE_PROP_STATS_ENABLED = "athenz.zpe.enable_stats"; + public static final String ZPE_PROP_METRIC_CLASS = "athenz.zpe.metric_factory_class"; + public static final String ZPE_PROP_PUBLIC_KEY_CLASS = "athenz.zpe.public_key_class"; + public static final String ZPE_PROP_CLIENT_IMPL = "athenz.zpe.updater_class"; + public static final String ZPE_PROP_TOKEN_OFFSET = "athenz.zpe.token_allowed_offset"; + public static final String ZPE_PROP_METRIC_WRITE_INTERVAL = "athenz.zpe.metric_write_interval"; + public static final String ZPE_PROP_METRIC_FILE_PATH = "athenz.zpe.metric_file_path"; + public static final String ZPE_PROP_MON_TIMEOUT = "athenz.zpe.monitor_timeout_secs"; + public static final String ZPE_PROP_MON_CLEANUP_TOKENS = "athenz.zpe.cleanup_tokens_secs"; + public static final String ZPE_PROP_POLICY_DIR = "athenz.zpe.policy_dir"; + + static final String ZPE_METRIC_FACTORY_CLASS = "com.yahoo.athenz.common.metrics.impl.NoOpMetricFactory"; + +} diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeMetric.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeMetric.java new file mode 100644 index 00000000000..35717ccf339 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeMetric.java @@ -0,0 +1,128 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import com.yahoo.athenz.zts.DomainMetric; +import com.yahoo.athenz.zts.DomainMetricType; +import com.yahoo.athenz.zts.DomainMetrics; +import com.yahoo.rdl.JSON; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicIntegerArray; +import java.util.ArrayList; +import java.util.Timer; + +public class ZpeMetric { + + public static final String ZPE_METRIC_FILE_PATH = "/var/zpe_stat/"; + public static final String ZPE_WRITE_INTERVAL = "3600000"; + + public ConcurrentHashMap counter = new ConcurrentHashMap<>(); + private static Timer FETCH_TIMER; + private static final Object TIMER_LOCK = new Object(); + static boolean statsEnabled = Boolean.parseBoolean(System.getProperty(ZpeConsts.ZPE_PROP_STATS_ENABLED, "false")); + + //constructor + ZpeMetric() { + File directory = new File(String.valueOf(getFilePath())); + directory.mkdir(); + //setting the timer to the interval specified in the system property + if (statsEnabled) { + Integer interval = Integer.parseInt(System.getProperty(ZpeConsts.ZPE_PROP_METRIC_WRITE_INTERVAL, ZPE_WRITE_INTERVAL)); + if (FETCH_TIMER == null) { + synchronized (TIMER_LOCK) { + if (FETCH_TIMER == null) { + FETCH_TIMER = new Timer(); + FETCH_TIMER.schedule(new SchedulerService(), interval, interval); + } + } + } + } + } + + //scheduler service + class SchedulerService extends TimerTask { + @Override + public void run() { + writeToFile(); + } + } + + String getFilePath() { + String rootDir = System.getenv("ROOT"); + if (rootDir == null) { + rootDir = "/home/athenz"; + } + final String defaultPath = rootDir + ZPE_METRIC_FILE_PATH; + String filePath = System.getProperty(ZpeConsts.ZPE_PROP_METRIC_FILE_PATH, defaultPath); + + // verify it ends with / and handle accordingly + + if (!filePath.endsWith(File.separator)) { + filePath = filePath.concat(File.separator); + } + return filePath; + } + + //to increment a metric counter by 1 + public void increment(String metricName, String domainName) { + if (statsEnabled) { + if (!counter.contains(domainName)) { + counter.putIfAbsent(domainName, new AtomicIntegerArray(DomainMetricType.LOAD_DOMAIN_GOOD.ordinal() + 1)); + } + Integer index = com.yahoo.athenz.zts.DomainMetricType.valueOf(metricName).ordinal(); + counter.get(domainName).incrementAndGet(index); + } + } + + //to convert the atomicIntegerArray to JSON object + public DomainMetrics getMetrics(String domainName) { + ArrayList metricList = new ArrayList<>(); + for (DomainMetricType label : DomainMetricType.values()) { + DomainMetric domainMetric = new DomainMetric(); + domainMetric.setMetricType(label); + domainMetric.setMetricVal(counter.get(domainName).getAndSet(label.ordinal(), 0)); + metricList.add(domainMetric); + } + DomainMetrics domainMetrics = new DomainMetrics() + .setDomainName(domainName) + .setMetricList(metricList); + return domainMetrics; + } + + //to write the JSON to file + public void writeToFile() { + final String dirPath = getFilePath(); + for (String domainName : counter.keySet()) { + DomainMetrics domainMetrics = getMetrics(domainName); + Long epoch = System.currentTimeMillis(); + String filepath = dirPath + domainName + "_" + Long.toString(epoch) + ".json"; + try { + Path path = Paths.get(filepath); + Files.write(path, JSON.bytes(domainMetrics)); + } catch (IOException e) { + e.printStackTrace(); + counter.remove(domainName); + } + } + } +} diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeThreadFactory.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeThreadFactory.java new file mode 100644 index 00000000000..9e9f1c7d4e5 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeThreadFactory.java @@ -0,0 +1,45 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +class ZpeThreadFactory implements ThreadFactory { + private static final AtomicInteger POOLNUMBER = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + public ZpeThreadFactory(String name) { + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : Thread.currentThread() + .getThreadGroup(); + namePrefix = name + "-pool-" + POOLNUMBER.getAndIncrement() + + "-thread-"; + } + + public Thread newThread(Runnable target) { + Thread thrd = new Thread(group, target, namePrefix + + threadNumber.getAndIncrement(), 0); + thrd.setDaemon(true); + if (thrd.getPriority() != Thread.NORM_PRIORITY) { + thrd.setPriority(Thread.NORM_PRIORITY); + } + return thrd; + } +} + diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdMonitor.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdMonitor.java new file mode 100644 index 00000000000..570b2c5afe5 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdMonitor.java @@ -0,0 +1,112 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import java.io.File; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Used in monitoring the policy directory for file changes. + */ +public class ZpeUpdMonitor implements Runnable { + private static final Logger LOG = LoggerFactory.getLogger(ZpeUpdMonitor.class); + + private final ZpeUpdPolLoader updLoader; + private volatile boolean shutdownThread = false; + private String dirName = null; + private boolean firstRun = true; + + private java.io.FilenameFilter polFileNameFilter = new java.io.FilenameFilter() { + public boolean accept(File dir, String name) { + if (name.endsWith(".pol")) { + return true; + } + return false; + } + }; + + ZpeUpdMonitor(final ZpeUpdPolLoader zpeUpdLoader) { + updLoader = zpeUpdLoader; + dirName = updLoader.getDirName(); + } + + public void cancel() { + shutdownThread = true; + } + + public File[] loadFileStatus() { + // read all the file names in the policy directory and add to the list + File pdir = new File(dirName); + File [] files = pdir.listFiles(polFileNameFilter); + if (files == null || files.length == 0) { + if (pdir.exists()) { + LOG.error("loadFileStatus: the directory=" + dirName + " exists, but there are no policy files in it"); + } else { + LOG.error("loadFileStatus: the directory=" + dirName + " does NOT exist"); + } + } + return files; + } + + private void logRunMsg(Exception exc) { + dirName = dirName == null ? "MISSING-POL-DIR-NAME" : dirName; + String msg = "Reload directory=" + dirName; + + if (exc == null) { + LOG.debug(msg); + } else { + LOG.error(msg, exc); + } + } + + @Override + public void run() { + if (updLoader == null) { + LOG.error("run: No ZpeUpdPolLoader to monitor"); + return; + } + + if (shutdownThread) { + LOG.warn("run: monitor told to shutdown"); + return; + } + + // perform cleanup of RoleTokens - expired ones will be removed + ZpeUpdPolLoader.cleanupRoleTokenCache(); + + try { + updLoader.loadDb(loadFileStatus()); + if (firstRun) { + firstRun = false; + synchronized (updLoader) { + updLoader.notify(); + } + } + } catch (Exception exc) { + logRunMsg(exc); + return; + } + + if (LOG.isDebugEnabled()) { + logRunMsg(null); + } + } + +} + diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdPolLoader.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdPolLoader.java new file mode 100644 index 00000000000..8ee0a92bbbf --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdPolLoader.java @@ -0,0 +1,486 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import java.io.Closeable; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Struct; +import com.yahoo.athenz.auth.token.RoleToken; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.utils.SignUtils; +import com.yahoo.athenz.zpe.match.ZpeMatch; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchAll; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchEqual; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchRegex; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchStartsWith; +import com.yahoo.athenz.zts.Assertion; +import com.yahoo.athenz.zts.AssertionEffect; +import com.yahoo.athenz.zts.DomainSignedPolicyData; +import com.yahoo.athenz.zts.Policy; +import com.yahoo.athenz.zts.PolicyData; +import com.yahoo.athenz.zts.SignedPolicyData; + +public class ZpeUpdPolLoader implements Closeable { + + private static final Logger LOG = LoggerFactory.getLogger(ZpeUpdPolLoader.class); + + static long _sleepTimeMillis = -1; + static long _cleanupTokenInterval = 600000; // 600 secs = 10 minutes + static long _lastTokenCleanup = System.currentTimeMillis(); + + static { + + String timeoutSecs = System.getProperty(ZpeConsts.ZPE_PROP_MON_TIMEOUT, null); + if (timeoutSecs == null) { + // default to 5 minutes + _sleepTimeMillis = TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES); + } else { + try { + long secs = Long.parseLong(timeoutSecs); + _sleepTimeMillis = TimeUnit.MILLISECONDS.convert(secs, TimeUnit.SECONDS); + } catch (NumberFormatException exc) { + String errMsg = "start: WARNING: Failed using system property(" + + ZpeConsts.ZPE_PROP_MON_TIMEOUT + + ") Got property value=" + timeoutSecs; + LOG.warn(errMsg, exc); + } + } + + timeoutSecs = System.getProperty(ZpeConsts.ZPE_PROP_MON_CLEANUP_TOKENS, null); + if (timeoutSecs != null) { + try { + long secs = Long.parseLong(timeoutSecs); + _cleanupTokenInterval = TimeUnit.MILLISECONDS.convert(secs, TimeUnit.SECONDS); + } catch (NumberFormatException exc) { + String errMsg = "start: WARNING: Failed using system property(" + + ZpeConsts.ZPE_PROP_MON_CLEANUP_TOKENS + + ") Got property value=" + timeoutSecs; + LOG.warn(errMsg, exc); + } + } + } + + // create thread or event handler to monitor changes to ZpePolFiles + // see JavaYnetDbWrapper for scheduled thread way to monitor + // find the java7 api for monitoring files + // see http://docs.oracle.com/javase/tutorial/essential/io/notification.html + private ScheduledThreadPoolExecutor scheduledExecutorSvc = new ScheduledThreadPoolExecutor( + 1, new ZpeThreadFactory("ZpeUpdPolLoader")); + + private ZpeUpdMonitor updMonWorker; + + // key is the domain name, value is a map keyed by role name with list of assertions + ConcurrentHashMap>> domStandardRoleAllowMap = new ConcurrentHashMap<>(); + + // wild card role map, keys and values same as domRoleMap above + ConcurrentHashMap>> domWildcardRoleAllowMap = new ConcurrentHashMap<>(); + + // key is the domain name, value is a map keyed by role name with list of assertions + ConcurrentHashMap>> domStandardRoleDenyMap = new ConcurrentHashMap<>(); + + // wild card role map, keys and values same as domRoleMap above + ConcurrentHashMap>> domWildcardRoleDenyMap = new ConcurrentHashMap<>(); + + // cache of active Role Tokens + static ConcurrentHashMap roleTokenCacheMap = new ConcurrentHashMap(); + + // array of file status objects + static class ZpeFileStatus { + String fname; + String domain; + long modifyTimeMillis; + boolean validPolFile; + + ZpeFileStatus(String fname, long modTimeMillis) { + domain = null; + modifyTimeMillis = modTimeMillis; + validPolFile = false; + } + } + private Map fileStatusRef = new ConcurrentHashMap(); + + private String polDirName; + + + ZpeUpdPolLoader(String dirName) { + + if (null != dirName) { + polDirName = dirName; + try { + loadDb(); + } catch (Exception exc) { + LOG.error("loadDb Failed", exc); + } + } + } + + String getDirName() { + return polDirName; + } + + Map getFileStatusMap() { + return fileStatusRef; + } + + // return map of wildcard role with assertion list with allow effect + // + public Map> getWildcardRoleAllowMap(String domainName) { + return domWildcardRoleAllowMap.get(domainName); + } + + // return map of role-name with assertion list with allow effect + // + public Map> getStandardRoleAllowMap(String domainName) { + return domStandardRoleAllowMap.get(domainName); + } + + // return map of wildcard role with assertion list with deny effect + // + public Map> getWildcardRoleDenyMap(String domainName) { + return domWildcardRoleDenyMap.get(domainName); + } + + // return map of role-name with assertion list with deny effect + // + public Map> getStandardRoleDenyMap(String domainName) { + return domStandardRoleDenyMap.get(domainName); + } + + static public Map getRoleTokenCacheMap() { + return roleTokenCacheMap; + } + + public void start() throws Exception { + if (polDirName == null) { + String errMsg = "ERROR: start: no policy directory name, can't monitor data files"; + throw new Exception(errMsg); + } + + if (updMonWorker == null) { + updMonWorker = new ZpeUpdMonitor(this); + } + scheduledExecutorSvc.scheduleAtFixedRate(updMonWorker, 0, + _sleepTimeMillis, TimeUnit.MILLISECONDS); + } + + @Override + public void close() { + if (updMonWorker != null) { + updMonWorker.cancel(); + } + scheduledExecutorSvc.shutdownNow(); + } + + static public void cleanupRoleTokenCache() { + // is it time to cleanup? + long now = System.currentTimeMillis(); + if (now < (_cleanupTokenInterval + _lastTokenCleanup)) { + return; + } + + List expired = new ArrayList(); + long nowSecs = now / 1000; + for (java.util.Enumeration keys = roleTokenCacheMap.keys(); + keys.hasMoreElements();) { + String key = keys.nextElement(); + RoleToken rToken = roleTokenCacheMap.get(key); + if (rToken == null) { + continue; + } + long expiry = rToken.getExpiryTime(); + if (expiry != 0 && expiry < nowSecs) { + expired.add(key); + if (LOG.isDebugEnabled()) { + LOG.debug("cleanupRoleTokenCache: Remove expired token. now(secs)=" + + nowSecs + " expiry=" + expiry + " token=" + key); + } + } + } + // HAVE: list of expired tokens + for (String key: expired) { + roleTokenCacheMap.remove(key); + } + _lastTokenCleanup = now; // reset time of last cleanup + } + + void loadDb() { + if (updMonWorker == null) { + updMonWorker = new ZpeUpdMonitor(this); + } + File[] polFileNames = updMonWorker.loadFileStatus(); + loadDb(polFileNames); + } + + /** + * Process the given policy file list and determine if any of the + * policy domain files have been updated. New ones will be loaded + * into the policy domain map. + **/ + void loadDb(File []polFileNames) { + if (polFileNames == null) { + LOG.error("loadDb: no policy files to load"); + return; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("loadDb: START thrd=" + Thread.currentThread().getId() + " directory=" + polDirName); + } + for (File polFile: polFileNames) { + + String fileName = polFile.getName(); + if (LOG.isDebugEnabled()) { + LOG.debug("loadDb: START thrd=" + Thread.currentThread().getId() + " file name=" + fileName); + } + long lastModMilliSeconds = polFile.lastModified(); + Map fsmap = getFileStatusMap(); + ZpeFileStatus fstat = fsmap.get(fileName); + if (fstat != null) { + + if (polFile.exists() == false) { // file was deleted + if (LOG.isDebugEnabled()) { + LOG.debug("loadDb: file(" + fileName + " ) was deleted or doesn't exist"); + } + fsmap.remove(fileName); + + if (fstat.validPolFile == false || fstat.domain == null) { + continue; + } + + // replace domain with empty data + // + domStandardRoleAllowMap.put(fstat.domain, new TreeMap>()); + domWildcardRoleAllowMap.put(fstat.domain, new TreeMap>()); + domStandardRoleDenyMap.put(fstat.domain, new TreeMap>()); + domWildcardRoleDenyMap.put(fstat.domain, new TreeMap>()); + continue; + } + + // check if file was modified since last time it was loaded + // + if (lastModMilliSeconds <= fstat.modifyTimeMillis) { + // if valid and up to date return + // if not valid, may be due to timing issue for a new + // file not completely written - and file system timestamp + // only accurate up to the second - not millis + String timeMsg = " last-file-mod-time=" + lastModMilliSeconds; + if (fstat.validPolFile == true) { + if (LOG.isDebugEnabled()) { + LOG.debug("loadDb: ignore reload file: " + fileName + " since up to date: " + timeMsg); + } + continue; + } else if (LOG.isDebugEnabled()) { + LOG.debug("loadDb: retry load file: " + fileName + " since last load was bad: " + timeMsg); + } + + } + } else { + fstat = new ZpeFileStatus(fileName, lastModMilliSeconds); + fsmap.put(fileName, fstat); + } + loadFile(polFile); + } + } + + ZpeMatch getMatchObject(String value) { + + ZpeMatch match = null; + if ("*".equals(value)) { + match = new ZpeMatchAll(); + } else { + int anyCharMatch = value.indexOf('*'); + int singleCharMatch = value.indexOf('?'); + + if (anyCharMatch == -1 && singleCharMatch == -1) { + match = new ZpeMatchEqual(value); + } else if (anyCharMatch == value.length() - 1 && singleCharMatch == -1) { + match = new ZpeMatchStartsWith(value.substring(0, value.length() - 1)); + } else { + match = new ZpeMatchRegex(value); + } + } + + return match; + } + + /** + * Loads and parses the given file. It will create the domain assertion + * list per role and put it into the domain policy maps(domRoleMap, domWildcardRoleMap). + **/ + private void loadFile(File polFile) { + if (LOG.isDebugEnabled()) { + LOG.debug("loadFile: file(" + polFile.getName() + ")"); + } + + Path path = Paths.get(polDirName + File.separator + polFile.getName()); + DomainSignedPolicyData spols = null; + try { + spols = JSON.fromBytes(Files.readAllBytes(path), DomainSignedPolicyData.class); + } catch (Exception ex) { + LOG.error("loadFile: unable to decode policy file=" + polFile.getName() + " error: " + ex.getMessage()); + } + if (spols == null) { + LOG.error("loadFile: unable to decode domain file=" + polFile.getName()); + // mark this as an invalid file + Map fsmap = getFileStatusMap(); + ZpeFileStatus fstat = fsmap.get(polFile.getName()); + if (fstat != null) { + fstat.validPolFile = false; + } + return; + } + + SignedPolicyData signedPolicyData = spols.getSignedPolicyData(); + String signature = spols.getSignature(); + String keyId = spols.getKeyId(); + + // first let's verify the ZTS signature for our policy file + + boolean verified = false; + if (signedPolicyData != null) { + java.security.PublicKey pubKey = AuthZpeClient.getZtsPublicKey(keyId); + verified = Crypto.verify(SignUtils.asCanonicalString(signedPolicyData), pubKey, signature); + } + + PolicyData policyData = null; + if (verified) { + // now let's verify that the ZMS signature for our policy file + policyData = signedPolicyData.getPolicyData(); + signature = signedPolicyData.getZmsSignature(); + keyId = signedPolicyData.getZmsKeyId(); + + if (policyData != null) { + java.security.PublicKey pubKey = AuthZpeClient.getZmsPublicKey(keyId); + verified = Crypto.verify(SignUtils.asCanonicalString(policyData), pubKey, signature); + } + } + + if (verified == false) { + LOG.error("loadFile: policy file=" + polFile.getName() + " is invalid"); + // mark this as an invalid file + Map fsmap = getFileStatusMap(); + ZpeFileStatus fstat = fsmap.get(polFile.getName()); + if (fstat != null) { + fstat.validPolFile = false; + } + return; + } + + // HAVE: valid policy file + + String domainName = policyData.getDomain(); + if (LOG.isDebugEnabled()) { + LOG.debug("loadFile: policy file(" + polFile.getName() + ") for domain(" + domainName + ") is valid"); + } + + // Process the policies into assertions, process the assertions: action, resource, role + // If there is a wildcard in the action or resource, compile the + // regexpr and place it into the assertion Struct. + // This is a performance enhancement for AuthZpeClient when it + // performs the authorization checks. + Map> roleStandardAllowMap = new TreeMap>(); + Map> roleWildcardAllowMap = new TreeMap>(); + Map> roleStandardDenyMap = new TreeMap>(); + Map> roleWildcardDenyMap = new TreeMap>(); + List policies = policyData.getPolicies(); + for (Policy policy : policies) { + String pname = policy.getName(); + if (LOG.isDebugEnabled()) { + LOG.debug("loadFile: domain(" + domainName + ") policy(" + pname + ")"); + } + List assertions = policy.getAssertions(); + if (assertions == null) { + continue; + } + for (Assertion assertion : assertions) { + com.yahoo.rdl.Struct strAssert = new Struct(); + strAssert.put(ZpeConsts.ZPE_FIELD_POLICY_NAME, pname); + + String passertAction = assertion.getAction(); + ZpeMatch matchStruct = getMatchObject(passertAction); + strAssert.put(ZpeConsts.ZPE_ACTION_MATCH_STRUCT, matchStruct); + + String passertResource = assertion.getResource(); + String rsrc = AuthZpeClient.stripDomainPrefix(passertResource, domainName, passertResource); + strAssert.put(ZpeConsts.ZPE_FIELD_RESOURCE, rsrc); + matchStruct = getMatchObject(rsrc); + strAssert.put(ZpeConsts.ZPE_RESOURCE_MATCH_STRUCT, matchStruct); + + String passertRole = assertion.getRole(); + String pRoleName = AuthZpeClient.stripDomainPrefix(passertRole, domainName, passertRole); + // strip the prefix "role." too + pRoleName = pRoleName.replaceFirst("^role.", ""); + strAssert.put(ZpeConsts.ZPE_FIELD_ROLE, pRoleName); + + // based on the effect and role name determine what + // map we're going to use + + Map> roleMap = null; + AssertionEffect passertEffect = assertion.getEffect(); + matchStruct = getMatchObject(pRoleName); + strAssert.put(ZpeConsts.ZPE_ROLE_MATCH_STRUCT, matchStruct); + + if (passertEffect != null && passertEffect.toString().compareTo("DENY") == 0) { + if (matchStruct instanceof ZpeMatchEqual) { + roleMap = roleStandardDenyMap; + } else { + roleMap = roleWildcardDenyMap; + } + } else { + if (matchStruct instanceof ZpeMatchEqual) { + roleMap = roleStandardAllowMap; + } else { + roleMap = roleWildcardAllowMap; + } + } + + List assertList = roleMap.get(pRoleName); + if (assertList == null) { + assertList = new ArrayList(); + roleMap.put(pRoleName, assertList); + } + assertList.add(strAssert); + } + } + + Map fsmap = getFileStatusMap(); + ZpeFileStatus fstat = fsmap.get(polFile.getName()); + if (fstat != null) { + fstat.validPolFile = true; + fstat.domain = domainName; + } + + domStandardRoleAllowMap.put(domainName, roleStandardAllowMap); + domWildcardRoleAllowMap.put(domainName, roleWildcardAllowMap); + domStandardRoleDenyMap.put(domainName, roleStandardDenyMap); + domWildcardRoleDenyMap.put(domainName, roleWildcardDenyMap); + } +} + diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdater.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdater.java new file mode 100644 index 00000000000..cd63898ca38 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeUpdater.java @@ -0,0 +1,99 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import java.io.File; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.token.RoleToken; +import com.yahoo.rdl.Struct; + +public class ZpeUpdater implements ZpeClient { + + private static final Logger LOG = LoggerFactory.getLogger(ZpeUpdater.class); + + // default policy directory "/home/athenz/var/zpe/" + private static final String ZPECLT_POLDIR_DEFAULT; + private static final ZpeUpdPolLoader POLICYLOADER; + + static { + String rootDir = System.getenv("ROOT"); + + if (null == rootDir) { + rootDir = File.separator + "home" + File.separator + "athenz"; + } + + ZPECLT_POLDIR_DEFAULT = rootDir + File.separator + "var" + + File.separator + "zpe"; + + String dirName = System.getProperty(ZpeConsts.ZPE_PROP_POLICY_DIR, ZPECLT_POLDIR_DEFAULT); + + try { + if (LOG.isDebugEnabled()) { + LOG.debug("static-init: start monitoring policy directory=" + dirName); + } + // load the file + POLICYLOADER = new ZpeUpdPolLoader(dirName); + + // this will start monitoring policy directory for file mods + POLICYLOADER.start(); + + } catch (Exception exc) { + LOG.error("static-init: failed loading policy files. System property(" + + ZpeConsts.ZPE_PROP_POLICY_DIR + ") Policy-directory(" + dirName + ")", + exc); + throw new RuntimeException(exc); + } + } + + // @param domain can be null + public void init(String domain) { + try { + synchronized (POLICYLOADER) { + POLICYLOADER.wait(5000); // wait max of 5 seconds + } + } catch (InterruptedException exc) { + LOG.warn("init: waiting for policy loader to be ready, continuing..."); + } + } + + // return current cache of role tokens collected from the remote clients + // + public Map getRoleTokenCacheMap() { + return ZpeUpdPolLoader.getRoleTokenCacheMap(); + } + + public Map> getWildcardAllowAssertions(String domain) { + return POLICYLOADER.getWildcardRoleAllowMap(domain); + } + + public Map> getRoleAllowAssertions(String domain) { + return POLICYLOADER.getStandardRoleAllowMap(domain); + } + + public Map> getWildcardDenyAssertions(String domain) { + return POLICYLOADER.getWildcardRoleDenyMap(domain); + } + + public Map> getRoleDenyAssertions(String domain) { + return POLICYLOADER.getStandardRoleDenyMap(domain); + } +} + diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeYcrKey.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeYcrKey.java new file mode 100644 index 00000000000..fa7f27e44d1 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeYcrKey.java @@ -0,0 +1,35 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +public class ZpeYcrKey { + + private String keyName; + private short version = 0; + + String getKeyName() { + return keyName; + } + short getVersion() { + return version; + } + void setKeyName(String value) { + keyName = value; + } + void setVersion(short value) { + version = value; + } +} diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/ZpeMatch.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/ZpeMatch.java new file mode 100644 index 00000000000..aa5d51c354e --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/ZpeMatch.java @@ -0,0 +1,24 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe.match; + +public interface ZpeMatch { + + /* + * @return boolean value if the given string matches for ZPE check + */ + public boolean matches(String value); +} diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchAll.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchAll.java new file mode 100644 index 00000000000..8df6c8120dd --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchAll.java @@ -0,0 +1,28 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe.match.impl; + +import com.yahoo.athenz.zpe.match.ZpeMatch; + +public class ZpeMatchAll implements ZpeMatch { + + public ZpeMatchAll() { + } + + public boolean matches(String value) { + return true; + } +} diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchEqual.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchEqual.java new file mode 100644 index 00000000000..962a2600ca9 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchEqual.java @@ -0,0 +1,30 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe.match.impl; + +import com.yahoo.athenz.zpe.match.ZpeMatch; + +public class ZpeMatchEqual implements ZpeMatch { + + private String matchValue; + public ZpeMatchEqual(String value) { + matchValue = value; + } + + public boolean matches(String value) { + return matchValue.equals(value); + } +} diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchRegex.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchRegex.java new file mode 100644 index 00000000000..2918713af1b --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchRegex.java @@ -0,0 +1,33 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe.match.impl; + +import java.util.regex.Pattern; + +import com.yahoo.athenz.zpe.AuthZpeClient; +import com.yahoo.athenz.zpe.match.ZpeMatch; + +public class ZpeMatchRegex implements ZpeMatch { + + private Pattern pattern; + public ZpeMatchRegex(String value) { + pattern = Pattern.compile(AuthZpeClient.patternFromGlob(value)); + } + + public boolean matches(String value) { + return pattern.matcher(value).matches(); + } +} diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchStartsWith.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchStartsWith.java new file mode 100644 index 00000000000..11c01b5e249 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/match/impl/ZpeMatchStartsWith.java @@ -0,0 +1,30 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe.match.impl; + +import com.yahoo.athenz.zpe.match.ZpeMatch; + +public class ZpeMatchStartsWith implements ZpeMatch { + + private String prefix; + public ZpeMatchStartsWith(String value) { + prefix = value; + } + + public boolean matches(String value) { + return value.startsWith(prefix); + } +} diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/PublicKeyStore.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/PublicKeyStore.java new file mode 100644 index 00000000000..32729541b94 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/PublicKeyStore.java @@ -0,0 +1,35 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe.pkey; + +import java.security.PublicKey; + +public interface PublicKeyStore { + + /** + * Returns the ZTS PublicKey object for the given identifier + * @param keyId key identifier + * @return PublicKey + */ + public PublicKey getZtsKey(String keyId); + + /** + * Returns the ZMS PublicKey object for the given identifier + * @param keyId key identifier + * @return PublicKey + */ + public PublicKey getZmsKey(String keyId); +} diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/PublicKeyStoreFactory.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/PublicKeyStoreFactory.java new file mode 100644 index 00000000000..1c110ad61cb --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/PublicKeyStoreFactory.java @@ -0,0 +1,25 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe.pkey; + +public interface PublicKeyStoreFactory { + + /** + * Create and return a new PublicKeyStore instance + * @return PublicKeyStore instance + */ + public PublicKeyStore create(); +} diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/file/FilePublicKeyStore.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/file/FilePublicKeyStore.java new file mode 100644 index 00000000000..7707d63ee5c --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/file/FilePublicKeyStore.java @@ -0,0 +1,107 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe.pkey.file; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.config.AthenzConfig; +import com.yahoo.athenz.zms.PublicKeyEntry; +import com.yahoo.athenz.zpe.ZpeConsts; +import com.yahoo.athenz.zpe.pkey.PublicKeyStore; +import com.yahoo.rdl.JSON; + +public class FilePublicKeyStore implements PublicKeyStore { + + private static final Logger LOG = LoggerFactory.getLogger(FilePublicKeyStore.class); + + private static final String ZPE_ATHENZ_CONFIG = "/conf/athenz/athenz.conf"; + + private Map ztsPublicKeyMap = new ConcurrentHashMap<>(); + private Map zmsPublicKeyMap = new ConcurrentHashMap<>(); + + public void init() { + + String rootDir = System.getenv("ROOT"); + if (rootDir == null) { + rootDir = "/home/athenz"; + } + + String confFileName = System.getProperty(ZpeConsts.ZPE_PROP_ATHENZ_CONF, + rootDir + ZPE_ATHENZ_CONFIG); + try { + Path path = Paths.get(confFileName); + AthenzConfig conf = JSON.fromBytes(Files.readAllBytes(path), AthenzConfig.class); + + loadPublicKeys(conf.getZtsPublicKeys(), ztsPublicKeyMap); + loadPublicKeys(conf.getZmsPublicKeys(), zmsPublicKeyMap); + + } catch (Exception ex) { + LOG.error("Unable to extract ZMS Url from {} exc: {}", + confFileName, ex.getMessage()); + return; + } + } + + void loadPublicKeys(ArrayList publicKeys, Map keyMap) { + + if (publicKeys == null) { + return; + } + + for (PublicKeyEntry publicKey : publicKeys) { + String id = publicKey.getId(); + String key = publicKey.getKey(); + if (key == null || id == null) { + continue; + } + PublicKey pubKey = null; + try { + pubKey = Crypto.loadPublicKey(Crypto.ybase64DecodeString(key)); + } catch (Exception e) { + LOG.error("Invalid ZTS public key for id: " + id + " - " + e.getMessage()); + continue; + } + keyMap.put(id, pubKey); + } + } + + @Override + public PublicKey getZtsKey(String keyId) { + if (keyId == null) { + return null; + } + return ztsPublicKeyMap.get(keyId); + } + + @Override + public PublicKey getZmsKey(String keyId) { + if (keyId == null) { + return null; + } + return zmsPublicKeyMap.get(keyId); + } + +} diff --git a/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/file/FilePublicKeyStoreFactory.java b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/file/FilePublicKeyStoreFactory.java new file mode 100644 index 00000000000..fb83a25f5f3 --- /dev/null +++ b/clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/pkey/file/FilePublicKeyStoreFactory.java @@ -0,0 +1,29 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe.pkey.file; + +import com.yahoo.athenz.zpe.pkey.PublicKeyStore; +import com.yahoo.athenz.zpe.pkey.PublicKeyStoreFactory; + +public class FilePublicKeyStoreFactory implements PublicKeyStoreFactory { + + @Override + public PublicKeyStore create() { + FilePublicKeyStore keyStore = new FilePublicKeyStore(); + keyStore.init(); + return keyStore; + } +} diff --git a/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/MockMetricFactory.java b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/MockMetricFactory.java new file mode 100644 index 00000000000..2448c644f24 --- /dev/null +++ b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/MockMetricFactory.java @@ -0,0 +1,94 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import java.util.Map; + +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.metrics.MetricFactory; + +import java.util.HashMap; + +public class MockMetricFactory implements MetricFactory { + + @Override + public Metric create() { + return new MockMetric(); + } + + public static class MockMetric implements Metric { + + Map metricMap = new HashMap<>(); + + public void increment(String metric, int count) { + Integer mcnt = metricMap.get(metric); + if (mcnt == null) { + metricMap.put(metric, new Integer(count)); + System.out.println("MockMetric:increment: " + metric + "=1"); + } else { + int cnt = mcnt.intValue() + count; + metricMap.put(metric, new Integer(cnt)); + System.out.println("MockMetric:increment: " + metric + "=" + cnt); + } + } + + @Override + public void increment(String metric) { + increment(metric, 1); + } + + @Override + public void increment(String metric, String domainName) { + increment(metric + domainName, 1); + } + + @Override + public void increment(String metric, String domainName, int count) { + increment(metric + domainName, count); + } + + public int metricCount(String metric) { + Integer icnt = metricMap.get(metric); + if (icnt == null) { + return -1; + } + return icnt.intValue(); + } + public int metricCount(String metric, String domainName) { + return metricCount(metric + domainName); + } + + @Override + public Object startTiming(String metric, String domainName) { + return null; + } + + @Override + public void stopTiming(Object timerMetric) { + } + + @Override + public void flush() { + metricMap.clear(); + } + + @Override + public void quit() { + metricMap.clear(); + } + } +} + diff --git a/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestAuthZpe.java b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestAuthZpe.java new file mode 100644 index 00000000000..f1fde18a277 --- /dev/null +++ b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestAuthZpe.java @@ -0,0 +1,1016 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertNotNull; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.token.RoleToken; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.utils.SignUtils; +import com.yahoo.athenz.zpe.AuthZpeClient; +import com.yahoo.athenz.zpe.ZpeUpdPolLoader; +import com.yahoo.athenz.zpe.AuthZpeClient.AccessCheckStatus; +import com.yahoo.athenz.zts.DomainSignedPolicyData; +import com.yahoo.athenz.zts.SignedPolicyData; +import com.yahoo.rdl.JSON; + +/** + * These tests are dependent on a policy file in a local dir. + */ +public class TestAuthZpe { + + private PrivateKey ztsPrivateKeyK0; + private PrivateKey ztsPrivateKeyK1; + private PrivateKey ztsPrivateKeyK17; + private PrivateKey ztsPrivateKeyK99; + private PrivateKey zmsPrivateKeyK0; + + private final String roleVersion = "Z1"; + private final long expirationTime = 100; // 100 seconds + private final String salt = "aAkjbbDMhnLX"; + + private RoleToken rToken0AnglerPublic = null; + private RoleToken rToken0AnglerExpirePublic = null; + private RoleToken rToken0AnglerAdmin = null; + private RoleToken rToken0SportsAdmin = null; + private RoleToken rToken1SportsAdmin = null; + private RoleToken rToken0AnglerPachinko = null; + private RoleToken rToken0CoreTechPublic = null; + private RoleToken rToken0EmptyPublic = null; + private RoleToken rToken0AnglerRegex = null; + + private static boolean sleepCompleted = false; + + @BeforeClass + public void beforeClass() throws IOException { + + Path path = Paths.get("./src/test/resources/zts_private_k0.pem"); + ztsPrivateKeyK0 = Crypto.loadPrivateKey(new String((Files.readAllBytes(path)))); + + path = Paths.get("./src/test/resources/zms_private_k0.pem"); + zmsPrivateKeyK0 = Crypto.loadPrivateKey(new String((Files.readAllBytes(path)))); + + path = Paths.get("./src/test/resources/zts_private_k1.pem"); + ztsPrivateKeyK1 = Crypto.loadPrivateKey(new String((Files.readAllBytes(path)))); + + path = Paths.get("./src/test/resources/zts_private_k17.pem"); + ztsPrivateKeyK17 = Crypto.loadPrivateKey(new String((Files.readAllBytes(path)))); + + path = Paths.get("./src/test/resources/zts_private_k99.pem"); + ztsPrivateKeyK99 = Crypto.loadPrivateKey(new String((Files.readAllBytes(path)))); + + List roles = new ArrayList(); + roles.add("public"); + rToken0AnglerPublic = createRoleToken("angler", roles, "0"); + rToken0AnglerExpirePublic = createRoleToken("angler", roles, "0", 3); + rToken0CoreTechPublic = createRoleToken("coretech", roles, "0"); + rToken0EmptyPublic = createRoleToken("empty", roles, "0"); + roles = new ArrayList(); + roles.add("admin"); + rToken0AnglerAdmin = createRoleToken("angler", roles, "0"); + rToken0SportsAdmin = createRoleToken("sports", roles, "0"); + rToken1SportsAdmin = createRoleToken("sports", roles, "1"); + + roles = new ArrayList(); + roles.add("pachinko"); + rToken0AnglerPachinko = createRoleToken("angler", roles, "0"); + + roles = new ArrayList(); + roles.add("full_regex"); + roles.add("matchall"); + roles.add("matchstarts"); + roles.add("matchcompare"); + roles.add("matchregex"); + rToken0AnglerRegex = createRoleToken("angler", roles, "0"); + + // NOTE: we will create file with different suffix so as not to confuse + // ZPE update-load thread due to possible timing issue. + // Then rename it with ".pol" suffix afterwards. + // Issue: file is created, but file is empty because it has not + // been written out yet - thus zpe thinks its a bad file and will + // wait for it to get updated before trying to reload. + // Ouch, but the file doesnt get a change in modified timestamp so zpe + // never reloads. + + path = Paths.get("./src/test/resources/angler.pol"); + DomainSignedPolicyData domainSignedPolicyData = JSON.fromBytes(Files.readAllBytes(path), + DomainSignedPolicyData.class); + SignedPolicyData signedPolicyData = domainSignedPolicyData.getSignedPolicyData(); + String signature = Crypto.sign(SignUtils.asCanonicalString(signedPolicyData.getPolicyData()), zmsPrivateKeyK0); + signedPolicyData.setZmsSignature(signature).setZmsKeyId("0"); + signature = Crypto.sign(SignUtils.asCanonicalString(signedPolicyData), ztsPrivateKeyK0); + domainSignedPolicyData.setSignature(signature).setKeyId("0"); + File file = new File("./src/test/resources/pol_dir/angler.gen"); + file.createNewFile(); + Files.write(file.toPath(), JSON.bytes(domainSignedPolicyData)); + File renamedFile = new File("./src/test/resources/pol_dir/angler.pol"); + file.renameTo(renamedFile); + + path = Paths.get("./src/test/resources/sports.pol"); + domainSignedPolicyData = JSON.fromBytes(Files.readAllBytes(path), DomainSignedPolicyData.class); + signedPolicyData = domainSignedPolicyData.getSignedPolicyData(); + signature = Crypto.sign(SignUtils.asCanonicalString(signedPolicyData.getPolicyData()), zmsPrivateKeyK0); + signedPolicyData.setZmsSignature(signature).setZmsKeyId("0"); + signature = Crypto.sign(SignUtils.asCanonicalString(signedPolicyData), ztsPrivateKeyK1); + domainSignedPolicyData.setSignature(signature).setKeyId("1"); + file = new File("./src/test/resources/pol_dir/sports.gen"); + file.createNewFile(); + Files.write(file.toPath(), JSON.bytes(domainSignedPolicyData)); + renamedFile = new File("./src/test/resources/pol_dir/sports.pol"); + file.renameTo(renamedFile); + + path = Paths.get("./src/test/resources/empty.pol"); + domainSignedPolicyData = JSON.fromBytes(Files.readAllBytes(path), DomainSignedPolicyData.class); + signedPolicyData = domainSignedPolicyData.getSignedPolicyData(); + signature = Crypto.sign(SignUtils.asCanonicalString(signedPolicyData.getPolicyData()), zmsPrivateKeyK0); + signedPolicyData.setZmsSignature(signature).setZmsKeyId("0"); + signature = Crypto.sign(SignUtils.asCanonicalString(signedPolicyData), ztsPrivateKeyK0); + domainSignedPolicyData.setSignature(signature).setKeyId("0"); + file = new File("./src/test/resources/pol_dir/empty.gen"); + file.createNewFile(); + Files.write(file.toPath(), JSON.bytes(domainSignedPolicyData)); + + renamedFile = new File("./src/test/resources/pol_dir/empty.pol"); + file.renameTo(renamedFile); + } + + @BeforeMethod + private void loadFiles() { + + if (sleepCompleted) { + return; + } + // sleep for a short period of time so the library has a chance + // to load all the policy files since that's done by a background + // thread + + AuthZpeClient.init(); + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + } + + sleepCompleted = true; + } + + private RoleToken createRoleToken(String svcDomain, List roles, String keyId, long expiry) { + RoleToken token = new RoleToken.Builder(roleVersion, svcDomain, roles) + .salt(salt).expirationWindow(expiry).keyId(keyId).build(); + + PrivateKey key = null; + if ("1".equals(keyId)) { + key = ztsPrivateKeyK1; + } else if ("0".equals(keyId)) { + key = ztsPrivateKeyK0; + } else if ("17".equals(keyId)) { + key = ztsPrivateKeyK17; + } else if ("99".equals(keyId)) { + key = ztsPrivateKeyK99; + } + + token.sign(key); + return token; + } + + private RoleToken createRoleToken(String svcDomain, List roles, String keyId) { + return createRoleToken(svcDomain, roles, keyId, expirationTime); + } + + @Test + public void testKeyIds() { + String action = "read"; + StringBuilder roleName = new StringBuilder(); + + //Test key id 0 on Angler domain + String angResource = "angler:stuff"; + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW, "rsrc=" + angResource + " act=" + action); + Assert.assertEquals(roleName.toString(), "public"); + + //Test key id 1 on Sports domain + roleName.setLength(0); + String resource = "sports.NFL_DB"; + status = AuthZpeClient.allowAccess(rToken1SportsAdmin, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW, "rsrc=" + resource + " act=" + action); + Assert.assertEquals(roleName.toString(), "admin"); + } + + @Test + public void testWrongKeyId() { + String action = "REad"; + StringBuilder roleName = new StringBuilder(); + + //Test key id 0 on Sports domain - should fail because its signed with key id 1 + String resource = "sports.NFL_DB"; + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0SportsAdmin, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "admin"); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0SportsAdmin.getSignedToken()); + tokenList.add(rToken0CoreTechPublic.getSignedToken()); + roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "admin"); + + // multi tokens test with duplicate tokens + tokenList = new ArrayList(); + tokenList.add(rToken0SportsAdmin.getSignedToken()); + tokenList.add(rToken0SportsAdmin.getSignedToken()); + roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "admin"); + } + + @Test + public void testPublicReadAllowedMixCaseActionResource() { + + String action = "REad"; + String angResource = "ANGler:stuff"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + status = AuthZpeClient.allowAccess(rToken0AnglerPublic.getSignedToken(), angResource, action); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + } + + @Test + public void testTokenExpired() { + String action = "REad"; + String angResource = "ANGler:stuff"; + StringBuilder roleName = new StringBuilder(); + + RoleToken tokenMock = Mockito.mock(RoleToken.class); + Mockito.when(tokenMock.getExpiryTime()).thenReturn(1L); // too old + + AccessCheckStatus status = AuthZpeClient.allowAccess(tokenMock, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_ROLETOKEN_EXPIRED); + } + + @Test + public void testPublicReadAllowed() { + String action = "read"; + String angResource = "angler:stuff"; + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic.getSignedToken(), angResource, action); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + } + + @Test + public void testPublicReadMismatchDomain() { + String action = "read"; + String angResource = "anglerTest:stuff"; + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic.getSignedToken(), angResource, action); + Assert.assertEquals(status, AccessCheckStatus.DENY_DOMAIN_MISMATCH); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0AnglerPublic.getSignedToken()); + tokenList.add(rToken0CoreTechPublic.getSignedToken()); + StringBuilder roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_DOMAIN_MISMATCH); + Assert.assertEquals(roleName.toString(), ""); + } + + @Test + public void testPublicReadDomainNotFound() { + String action = "read"; + String angResource = "CoreTech:stuff"; + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0CoreTechPublic.getSignedToken(), angResource, action); + Assert.assertEquals(status, AccessCheckStatus.DENY_DOMAIN_NOT_FOUND); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0AnglerPublic.getSignedToken()); + tokenList.add(rToken0CoreTechPublic.getSignedToken()); + StringBuilder roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_DOMAIN_NOT_FOUND); + Assert.assertEquals(roleName.toString(), ""); + } + + @Test + public void testPublicReadDomainEmpty() { + String action = "read"; + String angResource = "empty:stuff"; + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0EmptyPublic.getSignedToken(), angResource, action); + Assert.assertEquals(status, AccessCheckStatus.DENY_DOMAIN_EMPTY); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0AnglerPublic.getSignedToken()); + tokenList.add(rToken0EmptyPublic.getSignedToken()); + StringBuilder roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_DOMAIN_EMPTY); + Assert.assertEquals(roleName.toString(), ""); + } + + @Test + public void testPublicReadInvalidRoleToken() { + String action = "read"; + String angResource = "angler:stuff"; + + // make the token invalid by adding chars to the signature + String roleToken = rToken0AnglerPublic.getSignedToken(); + roleToken = roleToken.replace(";s=", ";s=ab"); + AccessCheckStatus status = AuthZpeClient.allowAccess(roleToken, angResource, action); + Assert.assertEquals(status, AccessCheckStatus.DENY_ROLETOKEN_INVALID); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0AnglerPublic.getSignedToken()); + tokenList.add(roleToken); // add the bad one in + StringBuilder roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "public"); + } + + @Test + public void testPublicReadExpiredRoleToken() { + String action = "read"; + String angResource = "angler:stuff"; + + // sleep 3 seconds so our token gets expired + + try { + Thread.sleep(3000); + } catch (Exception exc) { + } + + // the roletoken validate return false regardless if the token is + // invalid due to expiry or invalid signature. So we'll only + // the expired roletoken if we add it to the cache and then + // try to use it again, but the cache clear test case sets + // the timeout to 1secs so as soon as it's added, within a + // second it's removed so we can't wait until it's expired to + // test again. so for know we'll just get invalid token + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerExpirePublic.getSignedToken(), angResource, action); + Assert.assertEquals(status, AccessCheckStatus.DENY_ROLETOKEN_INVALID); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0AnglerPublic.getSignedToken()); + tokenList.add(rToken0AnglerExpirePublic.getSignedToken()); // add the expired one in + StringBuilder roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "public"); + } + + @Test + public void testPublicReadInvalidParameters() { + String action = "read"; + String angResource = "anglerTest:stuff"; + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic.getSignedToken(), "", action); + Assert.assertEquals(status, AccessCheckStatus.DENY_INVALID_PARAMETERS); + + status = AuthZpeClient.allowAccess(rToken0AnglerPublic.getSignedToken(), angResource, ""); + Assert.assertEquals(status, AccessCheckStatus.DENY_INVALID_PARAMETERS); + } + + @Test + public void testPublicWriteAllowed() { + String action = "write"; + String angResource = "angler:stuff"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "public"); + } + + @Test + public void testPublicWriteAllowedMixCaseActionResource() { + String action = "WRite"; + String angResource = "angLEr:STUff"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + } + + @Test + public void testPublicUknActDenied() { + String action = "WRiteREad"; + String angResource = "angler:stuff"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_NO_MATCH); + } + + @Test + public void testPublicThrowDenied() { + String action = "THrow"; + String angResource = "angler:stuff"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY); + } + + @Test + public void testAdminThrowAllowed() { + String action = "THrow"; + String angResource = "angler:stuff"; + StringBuilder roleName = new StringBuilder(); + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerAdmin, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + } + + @Test + public void testValidAccessResource() { + String action = "ACCESS"; + String angResource = "angler:tables.blah"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPachinko, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "pachinko"); + } + + @Test + public void testInvalidAccessResource() { + String action = "ACCESS"; + String angResource = "angler:tables.blahblah"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPachinko, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_NO_MATCH); + Assert.assertEquals(roleName.toString(), ""); + } + + @Test + public void testPublicFishingDenied() { + String action = "fish"; + String angResource = "angler:spawningpondLittleBassLake"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY); + Assert.assertEquals(roleName.toString(), "public"); + } + + @Test + public void testPublicFishingAllowed() { + String action = "fish"; + String angResource = "angler:stockedpondBigBassLake"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "public"); + } + + @Test + public void testPublicFishingAllowedTokenString() { + String action = "fish"; + String angResource = "angler:stockedpondBigBassLake"; + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerPublic.getSignedToken(), angResource, action); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + } + + @Test + public void testCleanupOfToken() { + // perform an allowed access check + String action = "fish"; + String angResource = "angler:stockedpondBigBassLake"; + List roles = new ArrayList(); + roles.add("public"); + roles.add("admin"); + RoleToken rtoken = createRoleToken("angler", roles, "0", 1); // 1 sec expiry + String signedToken = rtoken.getSignedToken(); + AccessCheckStatus status = AuthZpeClient.allowAccess(signedToken, angResource, action); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + + Map roleMap = ZpeUpdPolLoader.getRoleTokenCacheMap(); + RoleToken mapToken = roleMap.get(signedToken); + Assert.assertEquals(signedToken.equals(mapToken.getSignedToken()), true); + // then in a loop, check for existence of the token in the token map + // increase the timeout to 30 secs. in sd sometimes it takes a while + // before the entry is expired. + for (int cnt = 0; mapToken != null && cnt < 30; ++cnt) { + System.out.println("testCleanupOfToken: in loop, cnt=" + cnt + " token=" + signedToken); + // -Dyahoo.zpeclient.updater.monitor_timeout_secs=1 + // -Dyahoo.zpeclient.updater.cleanup_tokens_secs=1 + try { + Thread.sleep(1000); // test has timeout set to 1 second + } catch (Exception exc) { + System.out.println("testCleanupOfToken: sleep was interrupted: in loop, cnt=" + cnt + " token=" + signedToken); + } + + mapToken = roleMap.get(signedToken); + if (mapToken != null) { + Assert.assertEquals(signedToken.equals(mapToken.getSignedToken()), true); + } + } + // assert token is not in the map outside of the loop + Assert.assertNull(mapToken); + } + + @Test + public void testCleanupOfTokenNotCleaned() { + // perform an allowed access check + String action = "fish"; + String angResource = "angler:stockedpondBigBassLake"; + List roles = new ArrayList(); + roles.add("public"); + roles.add("admin"); + RoleToken rtoken = createRoleToken("angler", roles, "0", 10); // 10 sec expiry + String signedToken = rtoken.getSignedToken(); + AccessCheckStatus status = AuthZpeClient.allowAccess(signedToken, angResource, action); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + + Map roleMap = ZpeUpdPolLoader.getRoleTokenCacheMap(); + RoleToken mapToken = roleMap.get(signedToken); + Assert.assertEquals(signedToken.equals(mapToken.getSignedToken()), true); + // then in a loop, check for existence of the token in the token map + for (int cnt = 0; mapToken != null && cnt < 5; ++cnt) { + System.out.println("testCleanupOfToken: in loop, cnt=" + cnt + " token=" + signedToken); + // -Dyahoo.zpeclient.updater.monitor_timeout_secs=1 + // -Dyahoo.zpeclient.updater.cleanup_tokens_secs=1 + try { + Thread.sleep(1000); // test has timeout set to 1 second + } catch (Exception exc) { + System.out.println("testCleanupOfToken: sleep was interrupted: in loop, cnt=" + cnt + " token=" + signedToken); + } + + mapToken = roleMap.get(signedToken); + Assert.assertNotNull(mapToken); + Assert.assertEquals(signedToken.equals(mapToken.getSignedToken()), true); + } + // assert token is not in the map outside of the loop + Assert.assertNotNull(mapToken); + Assert.assertEquals(signedToken.equals(mapToken.getSignedToken()), true); + } + + @Test + public void testWildcardManagePondsKernDenied() { + String action = "manage"; + String angResource = "angler:pondsVenturaCounty"; + List roles = new ArrayList(); + roles.add("managerkernco"); + RoleToken rtoken = createRoleToken("angler", roles, "0", 1000); // 1000 sec expiry + StringBuilder roleName = new StringBuilder(256); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rtoken, angResource, action, roleName); + // Kern county manager not allowed to manage Ventura county ponds + Assert.assertEquals(status, AccessCheckStatus.DENY); + } + + @Test + public void testWildcardManagePondsKernAllowed() { + String action = "manage"; + String angResource = "angler:pondsKernCounty"; + List roles = new ArrayList(); + roles.add("managerkernco"); + RoleToken rtoken = createRoleToken("angler", roles, "0", 1000); // 1000 sec expiry + StringBuilder roleName = new StringBuilder(256); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rtoken, angResource, action, roleName); + // Ventura county manager is allowed to manage Kern county ponds + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "manager*"); + } + + @Test + public void testWildcardManageRiversKernAllowed() { + String action = "manage"; + String angResource = "angler:RiversKernCounty"; + List roles = new ArrayList(); + roles.add("managerkernco"); + RoleToken rtoken = createRoleToken("angler", roles, "0", 1000); // 1000 sec expiry + StringBuilder roleName = new StringBuilder(256); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rtoken, angResource, action, roleName); + // Ventura county manager is allowed to manage Kern county ponds + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "manager*"); + } + + @Test + public void testWildcardManagePondsVenturaAllowed() { + String action = "manage"; + String angResource = "angler:pondsKernCounty"; + List roles = new ArrayList(); + roles.add("managerventuraco"); + RoleToken rtoken = createRoleToken("angler", roles, "0", 1000); // 1000 sec expiry + StringBuilder roleName = new StringBuilder(256); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rtoken, angResource, action, roleName); + // Ventura county manager is allowed to manage Kern county ponds + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "manager*"); + } + + @Test + public void testWildcardManageRiversVenturaAllowed() { + String action = "manage"; + String angResource = "angler:RiversVenturaCounty"; + List roles = new ArrayList(); + roles.add("managerventuraco"); + RoleToken rtoken = createRoleToken("angler", roles, "0", 1000); // 1000 sec expiry + StringBuilder roleName = new StringBuilder(256); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rtoken, angResource, action, roleName); + // Ventura county manager is allowed to manage Kern county ponds + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + } + + @Test + public void testWildcardManageRiversVenturaDenied() { + String action = "manage"; + String angResource = "angler:RiversKernCounty"; + List roles = new ArrayList(); + roles.add("managerventuraco"); + RoleToken rtoken = createRoleToken("angler", roles, "0", 1000); // 1000 sec expiry + StringBuilder roleName = new StringBuilder(256); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rtoken, angResource, action, roleName); + // Ventura county manager is allowed to manage Kern county ponds + Assert.assertEquals(status, AccessCheckStatus.DENY); + Assert.assertEquals(roleName.toString(), "managerventura*"); + } + + @Test + public void testWildcardManagePondsAllowedTokenString() { + String action = "manage"; + String angResource = "angler:pondsKernCounty"; + List roles = new ArrayList(); + roles.add("managerkernco"); + roles.add("managerventuraco"); + RoleToken rtoken = createRoleToken("angler", roles, "0", 1000); // 1000 sec expiry + + AccessCheckStatus status = AuthZpeClient.allowAccess(rtoken.getSignedToken(), angResource, action); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + } + + @Test + public void testWildcardManageRiversDeniedTokenString() { + String action = "manage"; + String angResource = "angler:riversKernCounty"; + List roles = new ArrayList(); + roles.add("managerkernco"); + roles.add("managerventuraco"); + RoleToken rtoken = createRoleToken("angler", roles, "0", 1000); // 1000 sec expiry + + AccessCheckStatus status = AuthZpeClient.allowAccess(rtoken.getSignedToken(), angResource, action); + Assert.assertEquals(status, AccessCheckStatus.DENY); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0AnglerAdmin.getSignedToken()); // add an ALLOW role + tokenList.add(rtoken.getSignedToken()); // add the DENY role token in + StringBuilder roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY); // DENY over-rides ALLOW + Assert.assertEquals(roleName.toString(), "managerventura*"); + + tokenList = new ArrayList(); + tokenList.add(rToken0CoreTechPublic.getSignedToken()); // add a DENY_DOMAIN_MISMATCH + tokenList.add(rToken0AnglerAdmin.getSignedToken()); // add an ALLOW role + tokenList.add(rtoken.getSignedToken()); // add the DENY role token in + roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY); // DENY over-rides everything else + Assert.assertEquals(roleName.toString(), "managerventura*"); + + // order wont matter + tokenList = new ArrayList(); + tokenList.add(rtoken.getSignedToken()); // add the DENY role token in + tokenList.add(rToken0CoreTechPublic.getSignedToken()); // add a DENY_DOMAIN_MISMATCH + tokenList.add(rToken0AnglerAdmin.getSignedToken()); // add an ALLOW role + roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, angResource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY); // DENY over-rides everything else + Assert.assertEquals(roleName.toString(), "managerventura*"); + } + + @Test + public void testAllowAccessMatchAll() { + + String action = "all"; + String resource = "angler:stuff"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "matchall"); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0AnglerPublic.getSignedToken()); + tokenList.add(rToken0AnglerRegex.getSignedToken()); + roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "matchall"); + } + + @Test + public void testAllowAccessMatchStartsWithAllowed() { + + String action = "startswith"; + String resource = "angler:startswithgreat"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "matchstarts"); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0AnglerExpirePublic.getSignedToken()); + tokenList.add(rToken0AnglerRegex.getSignedToken()); + tokenList.add(rToken0AnglerPublic.getSignedToken()); + roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "matchstarts"); + } + + @Test + public void testAllowAccessMatchEqualAllowed() { + + String action = "compare"; + String resource = "angler:compare"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "matchcompare"); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0AnglerExpirePublic.getSignedToken()); + tokenList.add(rToken0AnglerRegex.getSignedToken()); + tokenList.add(rToken0AnglerPublic.getSignedToken()); + tokenList.add(rToken0CoreTechPublic.getSignedToken()); + roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "matchcompare"); + } + + @Test + public void testAllowAccessMatchRegexAllowed() { + + String action = "regex"; + String resource = "angler:nhllosangeleskings"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "matchregex"); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0AnglerExpirePublic.getSignedToken()); + tokenList.add(rToken0AnglerRegex.getSignedToken()); + tokenList.add(rToken0AnglerPublic.getSignedToken()); + tokenList.add(rToken0CoreTechPublic.getSignedToken()); + roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.ALLOW); + Assert.assertEquals(roleName.toString(), "matchregex"); + } + + @Test + public void testAllowAccessMatchStartsWithDenied() { + + String action = "startswith"; + String resource = "angler:startswitgreat"; /* missing h from startswith */ + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_NO_MATCH); + Assert.assertEquals(roleName.toString(), ""); + + // multi tokens test + List tokenList = new ArrayList(); + tokenList.add(rToken0AnglerExpirePublic.getSignedToken()); + tokenList.add(rToken0AnglerRegex.getSignedToken()); + tokenList.add(rToken0AnglerPublic.getSignedToken()); + tokenList.add(rToken0CoreTechPublic.getSignedToken()); + roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, resource, action, roleName); + // last token was for domain coretech + Assert.assertEquals(status, AccessCheckStatus.DENY_DOMAIN_MISMATCH); + Assert.assertEquals(roleName.toString(), ""); + + tokenList = new ArrayList(); + tokenList.add(rToken0AnglerExpirePublic.getSignedToken()); + tokenList.add(rToken0AnglerPublic.getSignedToken()); + tokenList.add(rToken0CoreTechPublic.getSignedToken()); + tokenList.add(rToken0AnglerRegex.getSignedToken()); + roleName = new StringBuilder(); + status = AuthZpeClient.allowAccess(tokenList, resource, action, roleName); + // last token was for domain angler with regex token + Assert.assertEquals(status, AccessCheckStatus.DENY_NO_MATCH); + Assert.assertEquals(roleName.toString(), ""); + + } + + @Test + public void testAllowAccessMatchEqualDenied() { + + String action = "compare"; + String resource = "angler:compares"; /* extra s after compare */ + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_NO_MATCH); + } + + @Test + public void testAllowAccessMatchRegexDenied() { + + String action = "regex"; + String resource = "angler:nhllosangeleskingsA"; /* extra A after kings */ + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_NO_MATCH); + } + + @Test + public void testAllowAccessMatchRegexInvalidOr1() { + + String action = "full_regex"; + String resource = "angler:coretech"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_NO_MATCH); + } + + @Test + public void testAllowAccessMatchRegexInvalidOr2() { + + String action = "full_regex"; + String resource = "angler:corecommit"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_NO_MATCH); + } + + @Test + public void testAllowAccessMatchRegexInvalidRange1() { + + String action = "full_regex"; + String resource = "angler:corea"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_NO_MATCH); + } + + @Test + public void testAllowAccessMatchRegexInvalidRange2() { + + String action = "full_regex"; + String resource = "angler:coreb"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_NO_MATCH); + } + + @Test + public void testAllowAccessMatchRegexInvalidRange3() { + + String action = "full_regex"; + String resource = "angler:coref"; + StringBuilder roleName = new StringBuilder(); + + AccessCheckStatus status = AuthZpeClient.allowAccess(rToken0AnglerRegex, resource, action, roleName); + Assert.assertEquals(status, AccessCheckStatus.DENY_NO_MATCH); + } + + @Test + public void testPatternFromGlob() { + assertEquals("^abc$", AuthZpeClient.patternFromGlob("abc")); + assertEquals("^abc.*$", AuthZpeClient.patternFromGlob("abc*")); + assertEquals("^abc.$", AuthZpeClient.patternFromGlob("abc?")); + assertEquals("^.*abc.$", AuthZpeClient.patternFromGlob("*abc?")); + assertEquals("^abc\\.abc:.*$", AuthZpeClient.patternFromGlob("abc.abc:*")); + assertEquals("^ab\\[a-c]c$", AuthZpeClient.patternFromGlob("ab[a-c]c")); + assertEquals("^ab.*\\.\\(\\)\\^\\$c$", AuthZpeClient.patternFromGlob("ab*.()^$c")); + assertEquals("^abc\\\\test\\\\$", AuthZpeClient.patternFromGlob("abc\\test\\")); + assertEquals("^ab\\{\\|c\\+$", AuthZpeClient.patternFromGlob("ab{|c+")); + assertEquals("^\\^\\$\\[\\(\\)\\\\\\+\\{\\..*.\\|$", AuthZpeClient.patternFromGlob("^$[()\\+{.*?|")); + } + + @Test + public void testValidateRoleToken() { + + List roles = new ArrayList(); + roles.add("public_role"); + RoleToken rToken = createRoleToken("coretech", roles, "0"); + + RoleToken validatedToken = AuthZpeClient.validateRoleToken(rToken.getSignedToken()); + assertNotNull(validatedToken); + assertEquals(validatedToken.getRoles().size(), 1); + assertEquals(validatedToken.getRoles().get(0), "public_role"); + assertEquals(validatedToken.getDomain(), "coretech"); + + // asking for the same token should return the data from our cache + + validatedToken = AuthZpeClient.validateRoleToken(rToken.getSignedToken()); + assertNotNull(validatedToken); + assertEquals(validatedToken.getRoles().size(), 1); + assertEquals(validatedToken.getRoles().get(0), "public_role"); + assertEquals(validatedToken.getDomain(), "coretech"); + } + + @Test + public void testValidateRoleTokenInvalidKeyVersion() { + + List roles = new ArrayList(); + roles.add("public_role"); + RoleToken rToken = createRoleToken("coretech", roles, "0"); + + String tamperedToken = rToken.getSignedToken().replace(";k=0;", ";k=zone1.invalid"); + RoleToken validatedToken = AuthZpeClient.validateRoleToken(tamperedToken); + assertNull(validatedToken); + } + + @Test + public void testValidateRoleTokenInvalidSignature() { + + List roles = new ArrayList(); + roles.add("public_role"); + RoleToken rToken = createRoleToken("coretech", roles, "0"); + + String tamperedToken = rToken.getSignedToken().replace(";s=", ";s=siginvalid"); + RoleToken validatedToken = AuthZpeClient.validateRoleToken(tamperedToken); + assertNull(validatedToken); + } + + @Test + public void testAccessCheckStatus() { + for (AccessCheckStatus stat : AccessCheckStatus.values()) { + assertNotNull(stat.toString()); + System.out.println("Debug for AccessCheckStatus()"); + System.out.println(stat.toString()); + } + } + + @Test + public void testInit() { + AuthZpeClient.init(); + } + + @Test + public void testgetZtsPublicKeyNull() throws Exception { + PublicKey key = AuthZpeClient.getZtsPublicKey("notexist"); + assertNull(key); + } +} + diff --git a/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeMatch.java b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeMatch.java new file mode 100644 index 00000000000..21413888a42 --- /dev/null +++ b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeMatch.java @@ -0,0 +1,101 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.zpe.ZpeUpdPolLoader; +import com.yahoo.athenz.zpe.match.ZpeMatch; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchAll; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchEqual; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchRegex; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchStartsWith; + +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertFalse; + +public class TestZpeMatch { + + @Test + public void testGetMatchAll() { + + try (ZpeUpdPolLoader loader = new ZpeUpdPolLoader(null)) { + + ZpeMatch matchObject = loader.getMatchObject("*"); + assertTrue(matchObject instanceof ZpeMatchAll); + + assertTrue(matchObject.matches("abc")); + assertTrue(matchObject.matches("false")); + assertTrue(matchObject.matches("whatever")); + } + } + + @Test + public void testGetMatchRegex() { + + try (ZpeUpdPolLoader loader = new ZpeUpdPolLoader(null)) { + + ZpeMatch matchObject = loader.getMatchObject("coretech?test*"); + assertTrue(matchObject instanceof ZpeMatchRegex); + + assertTrue(matchObject.matches("coretechAtest")); + assertTrue(matchObject.matches("coretechbtestgreat")); + + // failures + + assertFalse(matchObject.matches("whatever")); // random data + assertFalse(matchObject.matches("coretechtestgreat")); // missing ? + } + } + + @Test + public void testGetMatchEqual() { + + try (ZpeUpdPolLoader loader = new ZpeUpdPolLoader(null)) { + + ZpeMatch matchObject = loader.getMatchObject("coretech"); + assertTrue(matchObject instanceof ZpeMatchEqual); + + assertTrue(matchObject.matches("coretech")); + + // failures + + assertFalse(matchObject.matches("whatever")); // random data + assertFalse(matchObject.matches("coretechA")); // extra A + assertFalse(matchObject.matches("coretec")); // missing h + } + } + + @Test + public void testGetMatchStartsWith() { + + try (ZpeUpdPolLoader loader = new ZpeUpdPolLoader(null)) { + + ZpeMatch matchObject = loader.getMatchObject("coretech*"); + assertTrue(matchObject instanceof ZpeMatchStartsWith); + + assertTrue(matchObject.matches("coretech")); + assertTrue(matchObject.matches("coretechtest")); + assertTrue(matchObject.matches("coretechtesttest")); + + // failures + + assertFalse(matchObject.matches("whatever")); // random data + assertFalse(matchObject.matches("coretec")); // missing h + assertFalse(matchObject.matches("coretecA")); // missing h + extra A + } + } +} diff --git a/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeMetric.java b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeMetric.java new file mode 100644 index 00000000000..a1f861886eb --- /dev/null +++ b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeMetric.java @@ -0,0 +1,133 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import java.io.IOException; +import junit.framework.TestCase; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.io.File; +import org.testng.annotations.Test; + +import com.yahoo.athenz.zpe.AuthZpeClient; +import com.yahoo.athenz.zpe.ZpeConsts; +import com.yahoo.athenz.zpe.ZpeMetric; +import com.yahoo.athenz.zts.DomainMetric; +import com.yahoo.athenz.zts.DomainMetrics; +import com.yahoo.rdl.JSON; + +public class TestZpeMetric extends TestCase { + + @Test + public void testZpeMetric() throws IOException { + + // setting the system property to write in file every 5 secs + System.setProperty(ZpeConsts.ZPE_PROP_METRIC_WRITE_INTERVAL, "5000"); + + final String metricDirPath = "/tmp/zpe-metrics"; + File metricsDir = new File(metricDirPath); + metricsDir.mkdirs(); + System.setProperty(ZpeConsts.ZPE_PROP_METRIC_FILE_PATH, metricDirPath); + + final String TEST_DOMAIN = "test"; + ZpeMetric.statsEnabled = true; + ZpeMetric test = new ZpeMetric(); + + // cleaning the directory + File dir = new File(test.getFilePath()); + System.out.println(test.getFilePath()); + if (dir.exists()) { + for (File file: dir.listFiles()) { + if (!file.isDirectory()) { + file.delete(); + } + } + } else { + dir.mkdirs(); + } + + // incrementing metrics for testing + Integer index = com.yahoo.athenz.zts.DomainMetricType.valueOf(ZpeConsts.ZPE_METRIC_NAME).ordinal(); + System.out.println("Metric is now " + test.counter.get(index)); + test.increment(ZpeConsts.ZPE_METRIC_NAME, AuthZpeClient.DEFAULT_DOMAIN); + test.increment(ZpeConsts.ZPE_METRIC_NAME, AuthZpeClient.DEFAULT_DOMAIN); + test.increment(ZpeConsts.ZPE_METRIC_NAME, AuthZpeClient.DEFAULT_DOMAIN); + test.increment(ZpeConsts.ZPE_METRIC_NAME, AuthZpeClient.DEFAULT_DOMAIN); + test.increment(ZpeConsts.ZPE_METRIC_NAME, AuthZpeClient.DEFAULT_DOMAIN); + test.increment(ZpeConsts.ZPE_METRIC_NAME, AuthZpeClient.DEFAULT_DOMAIN); + System.out.println("Metric is now " + test.counter.get(index)); + + System.out.println("testZpeMetric Sleep Millisecs= 4000"); + try { + Thread.sleep(4000); + } catch (InterruptedException e) { + } + System.out.println("testZpeMetric: Nap over"); + + System.out.println("Metric is now " + test.counter.get(index)); + test.increment(ZpeConsts.ZPE_METRIC_NAME, TEST_DOMAIN); + test.increment(ZpeConsts.ZPE_METRIC_NAME, TEST_DOMAIN); + test.increment(ZpeConsts.ZPE_METRIC_NAME, AuthZpeClient.DEFAULT_DOMAIN); + test.increment(ZpeConsts.ZPE_METRIC_NAME, AuthZpeClient.DEFAULT_DOMAIN); + test.increment(ZpeConsts.ZPE_METRIC_NAME, AuthZpeClient.DEFAULT_DOMAIN); + test.increment(ZpeConsts.ZPE_METRIC_NAME, AuthZpeClient.DEFAULT_DOMAIN); + System.out.println("Metric is now " + test.counter.get(index)); + + System.out.println("testZpeMetric Sleep Millisecs= 2000"); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + } + System.out.println("testZpeMetric: Nap over"); + + // Reading from the json file generated + File[] filenames = dir.listFiles(); + + String filepath = test.getFilePath() + filenames[0].getName(); + Path path = Paths.get(filepath); + DomainMetrics domainMetrics = JSON.fromBytes(Files.readAllBytes(path), DomainMetrics.class); + // verifying the value of the metric + List metricList = domainMetrics.getMetricList(); + boolean metricVerified = false; + for (DomainMetric metric : metricList) { + if (metric.getMetricType().toString().equals(ZpeConsts.ZPE_METRIC_NAME)) { + assertEquals(10, metric.getMetricVal()); + metricVerified = true; + } + } + assertTrue(metricVerified); + + filepath = test.getFilePath() + filenames[1].getName(); + path = Paths.get(filepath); + domainMetrics = JSON.fromBytes(Files.readAllBytes(path), DomainMetrics.class); + // verifying the value of the metric + metricList = domainMetrics.getMetricList(); + metricVerified = false; + for (DomainMetric metric : metricList) { + if (metric.getMetricType().toString().equals(ZpeConsts.ZPE_METRIC_NAME)) { + assertEquals(2, metric.getMetricVal()); + metricVerified = true; + } + } + assertTrue(metricVerified); + + // unsetting the system property + System.clearProperty(ZpeConsts.ZPE_PROP_METRIC_WRITE_INTERVAL); + } + +} \ No newline at end of file diff --git a/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeUpdPolLoader.java b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeUpdPolLoader.java new file mode 100644 index 00000000000..5da685cc777 --- /dev/null +++ b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeUpdPolLoader.java @@ -0,0 +1,165 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import com.yahoo.athenz.zpe.match.ZpeMatch; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchAll; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchEqual; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchRegex; +import com.yahoo.athenz.zpe.match.impl.ZpeMatchStartsWith; + +import static org.testng.Assert.assertTrue; + +import java.io.File; + +public class TestZpeUpdPolLoader { + + static String TEST_POL_DIR = "./src/test/resources/upd_pol_dir/"; + static String TEST_POL_FILE = "angler.pol"; + static String TEST_POL_GOOD_FILE = "./src/test/resources/pol_dir/angler.pol"; + + static String TEST_POL_FILE_EMPTY = "empty.pol"; + static String TEST_POL_GOOD_FILE_EMPTY = "./src/test/resources/pol_dir/empty.pol"; + + @Test + public void testGetMatchObject() { + + try (ZpeUpdPolLoader loader = new ZpeUpdPolLoader(null)) { + + ZpeMatch matchObject = loader.getMatchObject("*"); + assertTrue(matchObject instanceof ZpeMatchAll); + + matchObject = loader.getMatchObject("**"); + assertTrue(matchObject instanceof ZpeMatchRegex); + + matchObject = loader.getMatchObject("?*"); + assertTrue(matchObject instanceof ZpeMatchRegex); + + matchObject = loader.getMatchObject("?"); + assertTrue(matchObject instanceof ZpeMatchRegex); + + matchObject = loader.getMatchObject("test?again*"); + assertTrue(matchObject instanceof ZpeMatchRegex); + + matchObject = loader.getMatchObject("*test"); + assertTrue(matchObject instanceof ZpeMatchRegex); + + matchObject = loader.getMatchObject("test"); + assertTrue(matchObject instanceof ZpeMatchEqual); + + matchObject = loader.getMatchObject("(test|again)"); + assertTrue(matchObject instanceof ZpeMatchEqual); + + matchObject = loader.getMatchObject("test*"); + assertTrue(matchObject instanceof ZpeMatchStartsWith); + } + } + + @Test + public void testLoadDb() throws Exception { + + System.out.println("TestZpeUpdPolLoader: testLoadDb: dir=" + TEST_POL_DIR); + + java.nio.file.Path dirPath = java.nio.file.Paths.get(TEST_POL_DIR); + try { + java.nio.file.Files.createDirectory(dirPath); + } catch (java.nio.file.FileAlreadyExistsException exc) { + } + + ZpeUpdPolLoader loader = new ZpeUpdPolLoader(TEST_POL_DIR); + + java.nio.file.Path badFile = java.nio.file.Paths.get(TEST_POL_DIR, TEST_POL_FILE); + java.nio.file.Files.deleteIfExists(badFile); + java.io.File polFile = new java.io.File(TEST_POL_DIR, TEST_POL_FILE); + polFile.createNewFile(); + java.io.File [] files = { polFile }; + loader.loadDb(files); + + long lastModMilliSeconds = polFile.lastModified(); + java.util.Map fsmap = loader.getFileStatusMap(); + ZpeUpdPolLoader.ZpeFileStatus fstat = fsmap.get(polFile.getName()); + assertTrue(fstat.validPolFile == false); + + // move good policy file over the bad one + java.nio.file.Path goodFile = java.nio.file.Paths.get(TEST_POL_GOOD_FILE); + java.nio.file.Files.copy(goodFile, badFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + + loader.loadDb(files); + long lastModMilliSeconds2 = polFile.lastModified(); + fsmap = loader.getFileStatusMap(); + fstat = fsmap.get(polFile.getName()); + assertTrue(fstat.validPolFile == true); + loader.close(); + System.out.println("TestZpeUpdPolLoader: testLoadDb: timestamp1=" + lastModMilliSeconds + " timestamp2=" + lastModMilliSeconds2); + } + + @Test + public void testLoadDBNull() { + ZpeUpdPolLoader loader = new ZpeUpdPolLoader(TEST_POL_DIR); + loader.loadDb(null); + + loader.close(); + } + + @Test + public void testLoadDBNotExist() throws Exception { + ZpeUpdPolLoader loader = new ZpeUpdPolLoader(TEST_POL_DIR); + File fileMock = Mockito.mock(File.class); + java.io.File [] files = { fileMock }; + + // delete file + Mockito.when(fileMock.exists()).thenReturn(false); + + try { + loader.loadDb(files); + } catch(Exception ex) { + loader.close(); + } + + loader.close(); + } + + @Test(expectedExceptions = {java.lang.Exception.class}) + public void testStartNullDir() throws Exception { + ZpeUpdPolLoader loader = new ZpeUpdPolLoader(null); + loader.start(); + loader.close(); + } + + @Test + public void testLoadFileStatusNull() { + ZpeUpdPolLoader loader = new ZpeUpdPolLoader("./noexist"); + ZpeUpdMonitor monitor = new ZpeUpdMonitor(loader); + File[] files = monitor.loadFileStatus(); + assertTrue(files == null); + loader.close(); + } + + @Test + public void testUpdLoaderInvalid() { + ZpeUpdPolLoader loaderMock = Mockito.mock(ZpeUpdPolLoader.class); + Mockito.when(loaderMock.getDirName()).thenReturn(null); + ZpeUpdMonitor monitor = new ZpeUpdMonitor(loaderMock); + + // TODO: validate log message + monitor.run(); + monitor.cancel(); + monitor.run(); + } +} diff --git a/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeYcrKey.java b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeYcrKey.java new file mode 100644 index 00000000000..ba570d43300 --- /dev/null +++ b/clients/java/zpe/src/test/java/com/yahoo/athenz/zpe/TestZpeYcrKey.java @@ -0,0 +1,35 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe; + +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +public class TestZpeYcrKey { + + @Test + public void testGetSetYcrKey() { + + ZpeYcrKey ycrkey = new ZpeYcrKey(); + + ycrkey.setKeyName("key1"); + ycrkey.setVersion((short) 0); + + assertEquals(ycrkey.getKeyName(), "key1"); + assertEquals(ycrkey.getVersion(), (short) 0); + } +} diff --git a/clients/java/zpe/src/test/resources/angler.pol b/clients/java/zpe/src/test/resources/angler.pol new file mode 100644 index 00000000000..414453c316e --- /dev/null +++ b/clients/java/zpe/src/test/resources/angler.pol @@ -0,0 +1,162 @@ +{ + "signedPolicyData": { + "expires": "2014-05-31T00:35:44.535Z", + "modified": "2014-05-24T00:34:45.922Z", + "policyData": { + "domain": "angler", + "policies": [ + { + "assertions": [ + { + "action": "*", + "role": "angler:role.admin", + "resource": "angler:*" + } + ], + "modified": "2014-05-24T00:34:45.922Z", + "name": "angler:policy.admin" + }, + { + "assertions": [ + { + "action": "read", + "role": "angler:role.public", + "resource": "angler:stuff" + }, + { + "effect": "ALLOW", + "action": "write", + "role": "angler:role.public", + "resource": "angler:stuff" + }, + { + "effect": "DENY", + "action": "throw", + "role": "angler:role.public", + "resource": "angler:stuff" + }, + { + "effect": "DENY", + "action": "fish", + "role": "angler:role.public", + "resource": "angler:spawningpond*" + }, + { + "effect": "ALLOW", + "action": "fish", + "role": "angler:role.public", + "resource": "angler:stockedpond*" + }, + { + "role": "angler:role.pachinko", + "action": "access", + "effect": "ALLOW", + "resource": "angler:tables.blah" + } + ], + "modified": "2014-05-24T00:35:11.387Z", + "name": "angler:policy.public" + }, + { + "assertions": [ + { + "action": "direct", + "role": "angler:role.director", + "resource": "angler:ponds*" + }, + { + "action": "oversee", + "role": "angler:role.foreman", + "resource": "angler:ponds*" + }, + { + "action": "manage", + "role": "angler:role.manager*", + "resource": "angler:ponds*" + }, + { + "effect": "DENY", + "action": "manage", + "role": "angler:role.managerkern*", + "resource": "angler:pondsventura*" + }, + { + "action": "direct", + "role": "angler:role.director", + "resource": "angler:rivers*" + }, + { + "action": "oversee", + "role": "angler:role.foreman", + "resource": "angler:rivers*" + }, + { + "action": "manage", + "role": "angler:role.manager*", + "resource": "angler:rivers*" + }, + { + "effect": "DENY", + "action": "manage", + "role": "angler:role.managerventura*", + "resource": "angler:riverskern*" + } + ], + "modified": "2014-05-24T00:34:45.922Z", + "name": "angler:policy.wildcardgamewardens" + }, + { + "assertions": [ + { + "action": "all", + "role": "angler:role.matchall", + "resource": "angler:*" + }, + { + "action": "startswith", + "role": "angler:role.matchstarts", + "resource": "angler:startswith*" + }, + { + "action": "compare", + "role": "angler:role.matchcompare", + "resource": "angler:compare" + }, + { + "action": "regex", + "role": "angler:role.matchregex", + "resource": "angler:nhl*kings" + } + ], + "modified": "2014-05-24T00:35:11.387Z", + "name": "angler:policy.matchtypes" + }, + { + "assertions": [ + { + "action": "full_regex", + "role": "angler:role.full_regex", + "resource": "angler:?ore(tech|commit)" + }, + { + "action": "full_regex", + "role": "angler:role.full_regex", + "resource": "angler:?ore[a-c]" + }, + { + "action": "full_regex", + "role": "angler:role.full_regex", + "resource": "angler:?ore[def]" + } + ], + "modified": "2014-05-24T00:35:11.387Z", + "name": "angler:policy.full_regex" + } + ] + }, + "zmsSignature": "eN5aZiqCof3HjHctjkYB2itNvI.90u.bq6zzLoXG8CJ8hSwG6IBMaIQ0yf.Q_SR5HTFi7Tmrc1quQuH17H0PJg--", + "zmsKeyId": "0" + }, + "signature": "o6G6eXPifUvvE87ElgpquAaSQGr0L0v_6HkcXJ972.v62eaedhMkJO5fPLS0RbmhQfiy_GPZsZNPB_gkULI4eFSsqfWb4hTtKgo1uqS6QoPc2KZDeXrGc_ocAqpB4l1qsj4y_o9vj8ICtHgLqRxIvmdajSPmy4xo0p8Us7q0pZ2_td8xN3ZsjMZDES78_grhRexcwnI_3a25jAWg_BzUZh5ynGUh221wAZZ3FzqL7CijKzHv.V7iUqOaqb4yziCtxEuWIkcpyQ8U77BA8jCxvwxoIEBbGXE_BM0Hp8G7sfFFxV6WojoY4YwHfVIPCOlG5C0fcH5gf0NzsvlUb7oOew--", + "keyId": "0" +} diff --git a/clients/java/zpe/src/test/resources/athenz.conf b/clients/java/zpe/src/test/resources/athenz.conf new file mode 100755 index 00000000000..3a1ec2da776 --- /dev/null +++ b/clients/java/zpe/src/test/resources/athenz.conf @@ -0,0 +1,24 @@ +{ + "zmsUrl": "https://dev.zms.athenzcompany.com:4443/", + "ztsUrl": "https://dev.zts.athenzcompany.com:4443/", + "ztsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FERmQzSjJWOFdDUy9nTkUyR1BCY3R1T1J5awpqZ1FtTGZXclRRRkVGYld4TU1mVUdtUm8vSnFHQ0h1SjE0TWU5aXJjSU5CcTdvZkxFdXhrQ3dZc3dsNE8zd3BPCnRyQTFmekdTRFo4RGpmWUxDbThjWUovWml4WG1FbW1yVk01UGxVYVlRNkJ6U0FRdnFuZzdXSjJZYkIySEZmU2EKZEJqNUN6YXJvVGRVMXdPNEV3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + }, + { + "id": "1", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FETGlLY1hjUDlrMWRJcGU4bm1OS3pBaWpGcApuY0VWbEFveS8xcHordE5ETjExcDQ0MTJEREhXejhFSUNiVkE0RE16Wm1ta09URFdlUDBQSWdnNTg0RlF1SGpsCmsyOWU4VjJXT3pqQWZybGlad0dKbm1mdlBhb3FOQkNhZDI3cWFubm1MOVU3cTcvSEdRWmpMeGdoaXhGa0FtczEKaHFlbnlkb2JSVkhheHV3cDB3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + } + ], + "zmsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + }, + { + "id": "1", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + } + ] +} diff --git a/clients/java/zpe/src/test/resources/athenz_no_0.conf b/clients/java/zpe/src/test/resources/athenz_no_0.conf new file mode 100755 index 00000000000..19132fc2b7c --- /dev/null +++ b/clients/java/zpe/src/test/resources/athenz_no_0.conf @@ -0,0 +1,24 @@ +{ + "zmsUrl": "https://dev.zms.athenzcompany.com:4443/", + "ztsUrl": "https://dev.zts.athenzcompany.com:4443/", + "ztsPublicKeys": [ + { + "id": "unused", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FDZVUvZ0pKODA0MmN6QVUwQk1ub3N6bS9BdgpabGFzVUY2U0FhOGVXQkhSVmFCOTJ3Ty9ZZHBTZ3BDalVnUXdGN3RzTCtSd3ZCVGVpOEJKRDhhTUE2MmFaNDhOCjZYWjc4ams2Sm53bHN4aWFsUXNkNXdDNkhWUUlTclVvSEM1UkxtdUFyRHlrSWo1dWVuVDhqcTA3Ky9sNWdMQnUKQkpMSlNrSlpJa3lzcVRqRmZRSURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + }, + { + "id": "1", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FETGlLY1hjUDlrMWRJcGU4bm1OS3pBaWpGcApuY0VWbEFveS8xcHordE5ETjExcDQ0MTJEREhXejhFSUNiVkE0RE16Wm1ta09URFdlUDBQSWdnNTg0RlF1SGpsCmsyOWU4VjJXT3pqQWZybGlad0dKbm1mdlBhb3FOQkNhZDI3cWFubm1MOVU3cTcvSEdRWmpMeGdoaXhGa0FtczEKaHFlbnlkb2JSVkhheHV3cDB3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + } + ], + "zmsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + }, + { + "id": "1", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + } + ] +} diff --git a/clients/java/zpe/src/test/resources/empty.pol b/clients/java/zpe/src/test/resources/empty.pol new file mode 100644 index 00000000000..fbf0105553a --- /dev/null +++ b/clients/java/zpe/src/test/resources/empty.pol @@ -0,0 +1,15 @@ +{ + "signedPolicyData": { + "modified": "2015-01-13T19:13:18.601Z", + "policyData": { + "domain": "empty", + "policies": [ + ] + }, + "zmsSignature": "eN5aZiqCof3HjHctjkYB2itNvI.90u.bq6zzLoXG8CJ8hSwG6IBMaIQ0yf.Q_SR5HTFi7Tmrc1quQuH17H0PJg--", + "zmsKeyId": "0", + "expires": "2015-01-20T19:13:18.599Z" + }, + "signature": "qJXtTXPI0aQcxUgerT9X4BM6HoKVM1pNClt86UViintmC5.IK9uYoFhtdMfDLq7Kzc8jdyT1iCqv_OmEyMHLCA--", + "keyId": "0" +} diff --git a/clients/java/zpe/src/test/resources/logback.xml b/clients/java/zpe/src/test/resources/logback.xml new file mode 100644 index 00000000000..0343d82c9e9 --- /dev/null +++ b/clients/java/zpe/src/test/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + System.out + + %-4relative [%thread] %-5level %class - %msg%n + + + + + + + + + diff --git a/clients/java/zpe/src/test/resources/logback_perf.xml b/clients/java/zpe/src/test/resources/logback_perf.xml new file mode 100644 index 00000000000..476b077502a --- /dev/null +++ b/clients/java/zpe/src/test/resources/logback_perf.xml @@ -0,0 +1,16 @@ + + + + + System.out + + %-4relative [%thread] %-5level %class - %msg%n + + + + + + + + + diff --git a/clients/java/zpe/src/test/resources/pol_dir/.keepme b/clients/java/zpe/src/test/resources/pol_dir/.keepme new file mode 100644 index 00000000000..e69de29bb2d diff --git a/clients/java/zpe/src/test/resources/sports.pol b/clients/java/zpe/src/test/resources/sports.pol new file mode 100644 index 00000000000..3ee12813eab --- /dev/null +++ b/clients/java/zpe/src/test/resources/sports.pol @@ -0,0 +1,32 @@ +{ + "signedPolicyData": { + "modified": "2015-01-13T19:13:18.601Z", + "policyData": { + "domain": "sports", + "policies": [ + { + "name": "sports:policy.admin", + "assertions": [ + { + "resource": "*", + "role": "sports:role.admin", + "action": "*", + "effect": "ALLOW" + }, + { + "resource": "*", + "role": "sports:role.non-admin", + "action": "*", + "effect": "DENY" + } + ] + } + ] + }, + "expires": "2015-01-20T19:13:18.599Z", + "zmsSignature": "eN5aZiqCof3HjHctjkYB2itNvI.90u.bq6zzLoXG8CJ8hSwG6IBMaIQ0yf.Q_SR5HTFi7Tmrc1quQuH17H0PJg--", + "zmsKeyId": "0" + }, + "signature": "qJXtTXPI0aQcxUgerT9X4BM6HoKVM1pNClt86UViintmC5.IK9uYoFhtdMfDLq7Kzc8jdyT1iCqv_OmEyMHLCA--", + "keyId": "0" +} diff --git a/clients/java/zpe/src/test/resources/sys.auth.pol b/clients/java/zpe/src/test/resources/sys.auth.pol new file mode 100644 index 00000000000..a9d1d898219 --- /dev/null +++ b/clients/java/zpe/src/test/resources/sys.auth.pol @@ -0,0 +1,32 @@ +{ + "signedPolicyData": { + "modified": "2015-01-13T19:13:37.745Z", + "policyData": { + "domain": "sys.auth", + "policies": [ + { + "name": "sys.auth:policy.admin", + "assertions": [ + { + "resource": "*", + "role": "sys.auth:role.admin", + "action": "*", + "effect": "ALLOW" + }, + { + "resource": "*", + "role": "sys.auth:role.non-admin", + "action": "*", + "effect": "DENY" + } + ] + } + ] + }, + "expires": "2015-01-20T19:13:37.744Z", + "zmsSignature": "eN5aZiqCof3HjHctjkYB2itNvI.90u.bq6zzLoXG8CJ8hSwG6IBMaIQ0yf.Q_SR5HTFi7Tmrc1quQuH17H0PJg--", + "zmsKeyId": "0" + }, + "signature": "eN5aZiqCof3HjHctjkYB2itNvI.90u.bq6zzLoXG8CJ8hSwG6IBMaIQ0yf.Q_SR5HTFi7Tmrc1quQuH17H0PJg--", + "keyId": "0" +} diff --git a/clients/java/zpe/src/test/resources/zms_private_k0.pem b/clients/java/zpe/src/test/resources/zms_private_k0.pem new file mode 100644 index 00000000000..356e1edf8bd --- /dev/null +++ b/clients/java/zpe/src/test/resources/zms_private_k0.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTqa+RwOH3ZcMKyoGxOJo9A +yeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQJARpQBv09w/j6O7TmgtJm4 +Ws5bIxMgOkrHaqPs2Epq8rX8FUyaYSHBL4yYhqWlno4j1TrLzWneni8kJCUNJydU +AQIhAPXBXPQ3UzzNeQC5gbcweMMIQOYHyF7W/NAEILSKgy6hAiEAxL7hOjpdYGtj +hjG1nVIs+HW6z5TnRig7JjTwqA8RMdECIQC4oPCIuRfb0jJaDQQa8FuJiqXXK3mp +ZrLARJmdiYJMgQIgZxpAnWsIlAay2RgjvJXbyzim9TFrIXDjzlnf47JBqIECIBue +3ht9BnawOQqGzFjD89dFQ6nZtbiAaqORj276/SzZ +-----END RSA PRIVATE KEY----- diff --git a/clients/java/zpe/src/test/resources/zms_public_k0.pem b/clients/java/zpe/src/test/resources/zms_public_k0.pem new file mode 100644 index 00000000000..727c3c67f22 --- /dev/null +++ b/clients/java/zpe/src/test/resources/zms_public_k0.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTq +a+RwOH3ZcMKyoGxOJo9AyeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQ== +-----END PUBLIC KEY----- diff --git a/clients/java/zpe/src/test/resources/zts_private_k0.pem b/clients/java/zpe/src/test/resources/zts_private_k0.pem new file mode 100644 index 00000000000..0f4e4f68ebc --- /dev/null +++ b/clients/java/zpe/src/test/resources/zts_private_k0.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDFd3J2V8WCS/gNE2GPBctuORykjgQmLfWrTQFEFbWxMMfUGmRo +/JqGCHuJ14Me9ircINBq7ofLEuxkCwYswl4O3wpOtrA1fzGSDZ8DjfYLCm8cYJ/Z +ixXmEmmrVM5PlUaYQ6BzSAQvqng7WJ2YbB2HFfSadBj5CzaroTdU1wO4EwIDAQAB +AoGAIH4nN5H5zhbyGjS2OPKbJuf+7pTv2dh2NFnXe3yXCTEdsKknHJ90TdnXejcc +PFwFcXN02COn9KKIg23M9lCFaWLhv/DtxW/pHMNPUmV26j8+mgFX7JsWdPLlobB0 +yTUjbv7RMk4UY+iHS4wsy1VtlPJtGWV96qyQW1BV3UgLxwECQQDwTuFOnsqTO+RL +Km9zXh0YnWWAADig53qTwtlJjY+siqccg2B111gF19DQJezoitrmgqDrfhbe9AeO +bwqHkHanAkEA0lxnpUYP0clRjDvZf2ey7Q0geCBv2KMGPXNvhoqV5KLGjy4jjON9 +UY86NpuTJxCG9mxgZ2MLtFrDM8yGXzAMtQJAPnAAUnEnqUGye2U3N/6ICNE8ghmM +nSIH00SZOGczoV0VNm9cLMIa+MmuU6bG+1S4s5PVQ9qrDprRK8zmK3r5mQJBAJto +uwWmAg/pnD5vBNsUIGLy3LcCt76jifuiKZWLEvwLqYej/Y2bjzzPBKHNQ+SLWDn7 +jSk0SjRfDXFaOzddhzUCQEI+I+tvOB0Vz7XdP9qr2hcSY1aL2YMCEA0sarZgPy81 +yWVgJcMwDR3MWGhz0AR0ao4WrKmg9JjtSUxvNbV82IU= +-----END RSA PRIVATE KEY----- diff --git a/clients/java/zpe/src/test/resources/zts_private_k1.pem b/clients/java/zpe/src/test/resources/zts_private_k1.pem new file mode 100644 index 00000000000..efbf892ebb6 --- /dev/null +++ b/clients/java/zpe/src/test/resources/zts_private_k1.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXwIBAAKBgQDLiKcXcP9k1dIpe8nmNKzAijFpncEVlAoy/1pz+tNDN11p4412 +DDHWz8EICbVA4DMzZmmkOTDWeP0PIgg584FQuHjlk29e8V2WOzjAfrliZwGJnmfv +PaoqNBCad27qannmL9U7q7/HGQZjLxghixFkAms1hqenydobRVHaxuwp0wIDAQAB +AoGBAI4mpuzMUtNOMzYd6ukJKQ5gdhpZv50eg/ESP055hEuRw4BNGWO1KKnq99px +TVI+RARGJso311AzuCp1jmFLHKjQwSeqBdO3a7dt3d6YUTkQg3bV+LuS0ruYstOb +moyDQ/pLRaFvK31Gd6t91Js1ZMm9XWyUZQBw3M9qdA0NaJYBAkEA7s2bgY9i8Vgh +n9qpDlv6+qgbZfEZK/maVJPH7mKWfT6AP0CBuHcLKv89506rYeyK+Vc2vAcQIkDu +82mVl1QlEwJBANow2KwHxiTwGM6GRMY00FwD1wB4WU3mzMtiWtrwaEChlWq1c8m8 +DtnX4rGVGxizYhKisZ8EEy5b2b/6tRTOQEECQQDoZMsq4IFnYV8Hk+HliXnLqQFQ +ybq3Yubf3Bk7UlIlfEeORpZ3D9Kce1yg15xNZccxM8CeZzk6PHrOAziC1m5lAkEA +kTdlXcmmxLrPp9SRPWG1MyiTFgsDVOfBcbO6SHEs0ac5bNXrhF6Xe8FFbW+RozTw +lvqVQQJTSc1z9WQE1R4YgQJBAK8PtvWSmMVUlwVa/np3vQCoeILI1ugjicCOLiq7 +b3Wj1aQoi3gXa746cxnG4Y8lzKdIm1ukCCdBc47/Y8ayIgY= +-----END RSA PRIVATE KEY----- diff --git a/clients/java/zpe/src/test/resources/zts_private_k17.pem b/clients/java/zpe/src/test/resources/zts_private_k17.pem new file mode 100644 index 00000000000..9e8891ce042 --- /dev/null +++ b/clients/java/zpe/src/test/resources/zts_private_k17.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC2fHq0j2Rs4FK6XQ7EM485H4suikgJOgkXX0p9efUU8vQpHvwD +iiJh1rmfBqAcxYjr1vXA5N0e0LV//qfxog6KPnDrbZ99FrkLYAtEL21riuyPrDhQ +pDnG+fpkI6jjL0XPXIML8Tg3n+6vusbnKF9PeO9to0UU6s76z3Gof8FhOwIDAQAB +AoGAFmSMAiAeaKXWQPwuVbEmABJ418ssL8WpW+7biy4t/tYZU/pzXlPTCEJ7IKKF +f0JWHOqR2Yu7/o4J15z+Ks6CDgto1VwDTFuz7Cwwx/L4QFLAAzZNBl1JDV7XrMMU +Vf1MSBiqIwLYm4sHSPmfW726PUqwF7oZOiGpbeFsWPgno/ECQQDwFAr4BwsGvAOD +oNL1NFVl24p35OW2U54kIW14tn3lFo03QrJG/EYPezYwrYzKeIl6mPyK9gqwUQ+x +m8ajzNIPAkEAwpapK6ia1yA96N0p3bXUiYU2J0ROx3ao7yc4qkZ2zzzGWWLeDvcs +Fa1YyU761WcXVC2/BAhdF87aJ4Fio5d6FQJBAKc0jl8oKJnMIHZwb/yvMjr6qHnQ +RdyyaBWp4mCMWSpQhpHSNfASi4kEuz0z4jaxtK5aFqmBqvgZvOBqKfKzGCkCQEvK +Tr3Yf+bGghaO/d2DEvM1VXBZ5K1ABHCRwDpiE6iILWFnZsJBd4RB7lEKWByCeM2q +u7mgYFIDmWjFtluthjkCQAz4rdNBmBnbte8dWpbEqxEiWfbnx1eLsiYnKVroNION +fQyEagDRqyo/nI9EWeQnEv+hTEJPY9JHJymlHWK9qXU= +-----END RSA PRIVATE KEY----- diff --git a/clients/java/zpe/src/test/resources/zts_private_k99.pem b/clients/java/zpe/src/test/resources/zts_private_k99.pem new file mode 100644 index 00000000000..1fe548c63db --- /dev/null +++ b/clients/java/zpe/src/test/resources/zts_private_k99.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC+xl5AphtG0NnPbheJl0/6hcKFN0NP0q61zzSW3IQ8W5Q1bEJl +5IhjqK7+S5oG6MoHxqQf1MINL5hj8y0ej9tP06DGSBrHH3eclTbaEABJ4+y1fWG7 +DFoJLWaSJkcnTcchcKKajPwhzLGWef+Ukitr6OybnhiyMRjMerraAfwbeQIDAQAB +AoGAewdCUT7N6GVXkcXVuA20rkMEpxR0fE3KDcWKjJ+5R5NniOxABaXTrzFhaO0o +b7xATbN0tHJokkhXZl20gzkSnNI8gQAKQSaA2Est8n4BHsWTVMiKYjOxnQ1cjkf+ +jQIyUEdS2e8F/B0CPYyGKopT2tByu1aQ0+x+3TfzmoMDFuECQQDwSMATi8QC5zDi +Tv2ZqXW5YfsfwRyFsQfOoQ8rmFZZrHRfoU1uJqO+LszTsDZTWJ0E4J+wlcLO8QPG ++52BA8iPAkEAy0Cl8Qmy6P5URQPiNGpUASWI6nMNgUIgPmRt+DsR4b40WW4IJ4Ig +d6xZPHTtoKsjkXEj2UePTwvcuODmNa6PdwJBANgte7GaI0VBXrecvYiL74BT6K0O +/mxBc3axbIaaTcXr499Nre4WEWc/j8Q8WwPtS4dh2An1Ewk/yVgyc+fo0X0CQQDF +9Jfp841JeXLvqMGmVSyt1TXNSfMMQjAPNFcancVjvJFVzGGqwQUIKVbcF/HcOvIw +VCYbF6QO07nMYlY0UGgvAkBnAqUUVUI59BOR3bAnG8kx75V77PZ7pA8uqoDq38Xp +xAVmrm8u8hLsiE9iROhCofhHj/r40hcHiyzwu3B/DSO3 +-----END RSA PRIVATE KEY----- diff --git a/clients/java/zpe/src/test/resources/zts_public_k0.pem b/clients/java/zpe/src/test/resources/zts_public_k0.pem new file mode 100644 index 00000000000..58b8bb295fd --- /dev/null +++ b/clients/java/zpe/src/test/resources/zts_public_k0.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDFd3J2V8WCS/gNE2GPBctuORyk +jgQmLfWrTQFEFbWxMMfUGmRo/JqGCHuJ14Me9ircINBq7ofLEuxkCwYswl4O3wpO +trA1fzGSDZ8DjfYLCm8cYJ/ZixXmEmmrVM5PlUaYQ6BzSAQvqng7WJ2YbB2HFfSa +dBj5CzaroTdU1wO4EwIDAQAB +-----END PUBLIC KEY----- diff --git a/clients/java/zpe/src/test/resources/zts_public_k1.pem b/clients/java/zpe/src/test/resources/zts_public_k1.pem new file mode 100644 index 00000000000..5c229adc4c1 --- /dev/null +++ b/clients/java/zpe/src/test/resources/zts_public_k1.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLiKcXcP9k1dIpe8nmNKzAijFp +ncEVlAoy/1pz+tNDN11p4412DDHWz8EICbVA4DMzZmmkOTDWeP0PIgg584FQuHjl +k29e8V2WOzjAfrliZwGJnmfvPaoqNBCad27qannmL9U7q7/HGQZjLxghixFkAms1 +hqenydobRVHaxuwp0wIDAQAB +-----END PUBLIC KEY----- diff --git a/clients/java/zpe/src/test/resources/zts_public_k17.pem b/clients/java/zpe/src/test/resources/zts_public_k17.pem new file mode 100644 index 00000000000..fd57e88f91b --- /dev/null +++ b/clients/java/zpe/src/test/resources/zts_public_k17.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2fHq0j2Rs4FK6XQ7EM485H4su +ikgJOgkXX0p9efUU8vQpHvwDiiJh1rmfBqAcxYjr1vXA5N0e0LV//qfxog6KPnDr +bZ99FrkLYAtEL21riuyPrDhQpDnG+fpkI6jjL0XPXIML8Tg3n+6vusbnKF9PeO9t +o0UU6s76z3Gof8FhOwIDAQAB +-----END PUBLIC KEY----- diff --git a/clients/java/zpe/src/test/resources/zts_public_k99.pem b/clients/java/zpe/src/test/resources/zts_public_k99.pem new file mode 100644 index 00000000000..2e1ec2d6823 --- /dev/null +++ b/clients/java/zpe/src/test/resources/zts_public_k99.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+xl5AphtG0NnPbheJl0/6hcKF +N0NP0q61zzSW3IQ8W5Q1bEJl5IhjqK7+S5oG6MoHxqQf1MINL5hj8y0ej9tP06DG +SBrHH3eclTbaEABJ4+y1fWG7DFoJLWaSJkcnTcchcKKajPwhzLGWef+Ukitr6Oyb +nhiyMRjMerraAfwbeQIDAQAB +-----END PUBLIC KEY----- diff --git a/clients/java/zts/README.md b/clients/java/zts/README.md new file mode 100644 index 00000000000..659ec0ef52f --- /dev/null +++ b/clients/java/zts/README.md @@ -0,0 +1,27 @@ +zts-java-client +=============== + +A Java client library to access ZTS. This client library is generated +from the RDL, and includes zts-core and all other dependencies. + +--- Connection Timeouts --- + +Default read and connect timeout values for ZTS Client connections +are 30000ms (30sec). The application can change these values by using +the following system properties: + + * athenz.zts.client.read_timeout + * athenz.zts.client.connect_timeout + +The values specified for timeouts must be in milliseconds. + +--- Prefetch Settings --- + + * athenz.zts.client.prefetch_auto_enable : true or false, default is false + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() + diff --git a/clients/java/zts/pom.xml b/clients/java/zts/pom.xml new file mode 100644 index 00000000000..22f2003e33e --- /dev/null +++ b/clients/java/zts/pom.xml @@ -0,0 +1,235 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../../pom.xml + + + zts_java_client + jar + zts-java-client + ZTS Java Client Library + + + + ${project.groupId} + zts_core + ${project.parent.version} + + + ${project.groupId} + sia_java_client + ${project.parent.version} + + + ${project.groupId} + client_common + ${project.parent.version} + + + ${project.groupId} + auth_core + ${project.parent.version} + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey.version} + + + com.fasterxml.jackson.core + jackson-annotations + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + org.glassfish.jersey.core + jersey-client + ${jersey.version} + + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.1.1 + + + + exec + + generate-sources + + + + bash + + scripts/make_stubs.sh + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + prepare-package + + jar + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + + + + + + org.glassfish.hk2 + org.glassfish.hk2.external + org.glassfish.jersey + org.glassfish.jersey.ext + org.glassfish.jersey.core + org.glassfish.jersey.media + org.glassfish.jersey.bundles.repackaged + com.fasterxml.jackson.core + com.fasterxml.jackson.jaxrs + com.fasterxml.jackson.module + javax.ws.rs + javax.annotation + javax.inject + jersey.repackaged.com.google.common + org.aopalliance + com.yahoo.athenz:sia_java_client + com.kohlschutter.junixsocket + + + + + javax.inject + athenz.shade.zts.javax.inject + + + org.aopalliance + athenz.shade.zts.org.aopalliance + + + jersey.repackaged.com.google.common + athenz.shade.zts.jersey.repackaged.com.google.common + + + javax.ws.rs + athenz.shade.zts.javax.ws.rs + + + javax.annotation + athenz.shade.zts.javax.annotation + + + org.glassfish.jersey + athenz.shade.zts.org.glassfish.jersey + + + org.glassfish.hk2 + athenz.shade.zts.org.glassfish.hk2 + + + org.glassfish.hk2.external + athenz.shade.zts.org.glassfish.hk2.external + + + org.jvnet.hk2 + athenz.shade.zts.org.jvnet.hk2 + + + org.jvnet.tiger_types + athenz.shade.zts.org.jvnet.tiger_types + + + com.fasterxml.jackson + athenz.shade.zts.com.fasterxml.jackson + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + + + coverage + + + coverage + + + + + + com.atlassian.maven.plugins + maven-clover2-plugin + 4.0.4 + + 1.8 + + + **/ZTSRDLGeneratedClient.java + **/*Mock.java + + ${HOME}/license/clover.license + + + + + + + diff --git a/clients/java/zts/scripts/make_stubs.sh b/clients/java/zts/scripts/make_stubs.sh new file mode 100755 index 00000000000..e046ac7f6c6 --- /dev/null +++ b/clients/java/zts/scripts/make_stubs.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# If the zts_core dependency has been updated, then this script should be run +# manually to pick up the latest rdl to generate the appropriate client library + +# Note this script is dependent on the rdl utility. +# go get github.com/ardielle/ardielle-tools/... + +command -v rdl >/dev/null 2>&1 || { + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "SOURCE WARNING"; + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "Please install rdl utility: go get github.com/ardielle/ardielle-tools/..."; + echo >&2 "Skipping source generation..."; + exit 0; +} + +RDL_FILE=../../../core/zts/src/main/rdl/ZTS.rdl + +echo "Generate the client library..." +rdl -s generate -o src/main/java -x clientclass=ZTSRDLGenerated java-client $RDL_FILE diff --git a/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ResourceError.java b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ResourceError.java new file mode 100644 index 00000000000..ddd96536251 --- /dev/null +++ b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ResourceError.java @@ -0,0 +1,24 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zts; + +public class ResourceError { + + public int code; + public String message; + + public ResourceError code(int code) { + this.code = code; + return this; + } + public ResourceError message(String message) { + this.message = message; + return this; + } + + public String toString() { + return "{code: " + code + ", message: \"" + message + "\"}"; + } + +} diff --git a/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ResourceException.java b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ResourceException.java new file mode 100644 index 00000000000..4d094b4ee5c --- /dev/null +++ b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ResourceException.java @@ -0,0 +1,79 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zts; + +public class ResourceException extends RuntimeException { + public final static int OK = 200; + public final static int CREATED = 201; + public final static int ACCEPTED = 202; + public final static int NO_CONTENT = 204; + public final static int MOVED_PERMANENTLY = 301; + public final static int FOUND = 302; + public final static int SEE_OTHER = 303; + public final static int NOT_MODIFIED = 304; + public final static int TEMPORARY_REDIRECT = 307; + public final static int BAD_REQUEST = 400; + public final static int UNAUTHORIZED = 401; + public final static int FORBIDDEN = 403; + public final static int NOT_FOUND = 404; + public final static int CONFLICT = 409; + public final static int GONE = 410; + public final static int PRECONDITION_FAILED = 412; + public final static int UNSUPPORTED_MEDIA_TYPE = 415; + public final static int INTERNAL_SERVER_ERROR = 500; + public final static int NOT_IMPLEMENTED = 501; + + public final static int SERVICE_UNAVAILABLE = 503; + + public static String codeToString(int code) { + switch (code) { + case OK: return "OK"; + case CREATED: return "Created"; + case ACCEPTED: return "Accepted"; + case NO_CONTENT: return "No Content"; + case MOVED_PERMANENTLY: return "Moved Permanently"; + case FOUND: return "Found"; + case SEE_OTHER: return "See Other"; + case NOT_MODIFIED: return "Not Modified"; + case TEMPORARY_REDIRECT: return "Temporary Redirect"; + case BAD_REQUEST: return "Bad Request"; + case UNAUTHORIZED: return "Unauthorized"; + case FORBIDDEN: return "Forbidden"; + case NOT_FOUND: return "Not Found"; + case CONFLICT: return "Conflict"; + case GONE: return "Gone"; + case PRECONDITION_FAILED: return "Precondition Failed"; + case UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type"; + case INTERNAL_SERVER_ERROR: return "Internal Server Error"; + case NOT_IMPLEMENTED: return "Not Implemented"; + default: return "" + code; + } + } + + int code; + Object data; + + public ResourceException(int code) { + this(code, new ResourceError().code(code).message(codeToString(code))); + } + + public ResourceException(int code, Object data) { + super("ResourceException (" + code + "): " + data); + this.code = code; + this.data = data; + } + + public int getCode() { + return code; + } + + public Object getData() { + return data; + } + + public T getData(Class cl) { + return cl.cast(data); + } + +} diff --git a/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClient.java b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClient.java new file mode 100644 index 00000000000..6dbf44205ca --- /dev/null +++ b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClient.java @@ -0,0 +1,1533 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.cert.Certificate; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.ServiceLoader; +import java.util.Set; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; + +import org.glassfish.jersey.client.ClientProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.RoleAuthority; +import com.yahoo.athenz.common.config.AthenzConfig; +import com.yahoo.athenz.sia.SIA; +import com.yahoo.athenz.sia.impl.SIAClient; +import com.yahoo.rdl.JSON; + +public class ZTSClient implements Closeable { + + private static final Logger LOG = LoggerFactory.getLogger(ZTSClient.class); + + private String ztsUrl = null; + private String domain = null; + private String service = null; + protected ZTSRDLGeneratedClient ztsClient; + protected SIA siaClient = null; + + // configurable fields + // + static private boolean cacheDisabled = false; + static private int tokenMinExpiryTime = 900; + static private int siaTokenMinExpiryTime = 300; + static private int siaTokenMaxExpiryTime = 3600; + static private long prefetchInterval = 60; // seconds + static private boolean prefetchAutoEnable = false; + + @SuppressWarnings("unused") + static private boolean initialized = initConfigValues(); + + private boolean enablePrefetch = true; + Principal principal = null; + + // system properties + + public static final String ZTS_CLIENT_PROP_ATHENZ_CONF = "athenz.athenz_conf"; + + public static final String ZTS_CLIENT_PROP_TOKEN_MIN_EXPIRY_TIME = "athenz.zts.client.token_min_expiry_time"; + public static final String ZTS_CLIENT_PROP_TOKEN_SIA_MIN_EXPIRY_TIME = "athenz.zts.client.sia_token_min_expiry_time"; + public static final String ZTS_CLIENT_PROP_TOKEN_SIA_MAX_EXPIRY_TIME = "athenz.zts.client.sia_token_max_expiry_time"; + public static final String ZTS_CLIENT_PROP_READ_TIMEOUT = "athenz.zts.client.read_timeout"; + public static final String ZTS_CLIENT_PROP_CONNECT_TIMEOUT = "athenz.zts.client.connect_timeout"; + public static final String ZTS_CLIENT_PROP_PREFETCH_SLEEP_INTERVAL = "athenz.zts.client.prefetch_sleep_interval"; + public static final String ZTS_CLIENT_PROP_PREFETCH_AUTO_ENABLE = "athenz.zts.client.prefetch_auto_enable"; + public static final String ZTS_CLIENT_PROP_X509CERT_DNS_NAME = "athenz.zts.client.x509cert_dns_name"; + public static final String ZTS_CLIENT_PROP_DISABLE_CACHE = "athenz.zts.client.disable_cache"; + + public static final String ROLE_TOKEN_HEADER = System.getProperty(RoleAuthority.ATHENZ_PROP_ROLE_HEADER, + RoleAuthority.HTTP_HEADER); + + final static ConcurrentHashMap ROLE_TOKEN_CACHE = new ConcurrentHashMap<>(); + static ConcurrentHashMap awsCredCache = new ConcurrentHashMap<>(); + + private static final long FETCH_EPSILON = 60; // if cache expires in the next minute, fetch it. + private static final Queue PREFETCH_SCHEDULED_ITEMS = new ConcurrentLinkedQueue<>(); + private static Timer FETCH_TIMER; + private static final Object TIMER_LOCK = new Object(); + static AtomicLong FETCHER_LAST_RUN_AT = new AtomicLong(-1); + + // allows outside implementations to get role tokens for special environments - ex. hadoop + + private static final ServiceLoader ZTS_TOKEN_PROVIDERS = ServiceLoader.load(ZTSClientService.class); + private static final AtomicReference> SVC_LOADER_CACHE_KEYS = new AtomicReference<>(); + static { + loadSvcProviderTokens(); + } + + static boolean initConfigValues() { + + /* The minimum token expiry time by default is 15 minutes (900). By default the + * server gives out role tokens for 2 hours and with this setting we'll be able + * to cache tokens for 1hr45mins before requesting a new one from ZTS */ + + tokenMinExpiryTime = Integer.parseInt(System.getProperty(ZTS_CLIENT_PROP_TOKEN_MIN_EXPIRY_TIME, "900")); + if (tokenMinExpiryTime < 0) { + tokenMinExpiryTime = 900; + } + + /* We're going to cache Service Principal Tokens as long as possible so by default + * we're going to request tokens with a minimum timeout of 5 mins and max default + * value of 1hr. SIA Client Library provides the caching support so we'll just + * make the call for every request and let the SIA client library to return the + * cached token back to us */ + + siaTokenMinExpiryTime = Integer.parseInt(System.getProperty(ZTS_CLIENT_PROP_TOKEN_SIA_MIN_EXPIRY_TIME, "300")); + if (siaTokenMinExpiryTime < 0) { + siaTokenMinExpiryTime = 300; + } + siaTokenMaxExpiryTime = Integer.parseInt(System.getProperty(ZTS_CLIENT_PROP_TOKEN_SIA_MAX_EXPIRY_TIME, "3600")); + if (siaTokenMaxExpiryTime < 0) { + siaTokenMaxExpiryTime = 3600; + } + + prefetchInterval = Integer.parseInt(System.getProperty(ZTS_CLIENT_PROP_PREFETCH_SLEEP_INTERVAL, "60")); + if (prefetchInterval >= tokenMinExpiryTime) { + prefetchInterval = 60; + } + + prefetchAutoEnable = Boolean.parseBoolean(System.getProperty(ZTS_CLIENT_PROP_PREFETCH_AUTO_ENABLE, "false")); + cacheDisabled = Boolean.parseBoolean(System.getProperty(ZTS_CLIENT_PROP_DISABLE_CACHE, "false")); + return true; + } + + /** + * Constructs a new ZTSClient object with the given principal identity + * and media type set to application/json. The url for ZTS Server is + * automatically retrieved from the athenz_config package's configuration + * file (zts_url field). + * Default read and connect timeout values are 30000ms (30sec). The application can + * change these values by using the yahoo.zts_java_client.read_timeout and + * yahoo.zts_java_client.connect_timeout system properties. The values specified + * for timeouts must be in milliseconds. + * @param identity Principal identity for authenticating requests + */ + public ZTSClient(Principal identity) { + initClient(null, identity, null, null); + enablePrefetch = false; // cant use this domain and service for prefetch + } + + /** + * Constructs a new ZTSClient object with the given principal identity + * and ZTS Server URL and media type set to application/json. + * Default read and connect timeout values are 30000ms (30sec). The application can + * change these values by using the yahoo.zts_java_client.read_timeout and + * yahoo.zts_java_client.connect_timeout system properties. The values specified + * for timeouts must be in milliseconds. + * @param url ZTS Server's URL + * @param identity Principal identity for authenticating requests + */ + public ZTSClient(String url, Principal identity) { + initClient(url, identity, null, null); + enablePrefetch = false; // cant use this domain and service for prefetch + } + + /** + * Constructs a new ZTSClient object with the given service identity + * and media type set to application/json. The url for ZTS Server is + * automatically retrieved from the athenz_config package's configuration + * file (zts_url field). The service's principal token will be retrieved + * from the SIA Client. + * Default read and connect timeout values are 30000ms (30sec). The application can + * change these values by using the yahoo.zts_java_client.read_timeout and + * yahoo.zts_java_client.connect_timeout system properties. The values specified + * for timeouts must be in milliseconds. + * @param domainName name of the domain + * @param serviceName name of the service + */ + public ZTSClient(String domainName, String serviceName) { + initClient(null, null, domainName, serviceName); + } + + /** + * Constructs a new ZTSClient object with the given service identity + * and media type set to application/json. The service's principal + * token will be retrieved from the SIA Client. + * Default read and connect timeout values are 30000ms (30sec). The application can + * change these values by using the yahoo.zts_java_client.read_timeout and + * yahoo.zts_java_client.connect_timeout system properties. The values specified + * for timeouts must be in milliseconds. + * @param url ZTS Server's URL + * @param domainName name of the domain + * @param serviceName name of the service + */ + public ZTSClient(String url, String domainName, String serviceName) { + initClient(url, null, domainName, serviceName); + } + + /** + * Close the ZTSClient object and release any allocated resources. + */ + @Override + public void close() { + ztsClient.close(); + } + + void removePrefetcher() { + PREFETCH_SCHEDULED_ITEMS.clear(); + if (FETCH_TIMER != null) { + FETCH_TIMER.purge(); + FETCH_TIMER.cancel(); + FETCH_TIMER = null; + } + } + + /** + * Returns the locally configured ZTS Server's URL value + * @return ZTS Server URL + */ + public String getZTSUrl() { + return ztsUrl; + } + + public void setZTSRDLGeneratedClient(ZTSRDLGeneratedClient client) { + this.ztsClient = client; + } + + public void setSIAClient(SIA client) { + this.siaClient = client; + } + + String lookupZTSUrl() { + + String rootDir = System.getenv("ROOT"); + if (rootDir == null) { + rootDir = "/home/athenz"; + } + + String confFileName = System.getProperty(ZTS_CLIENT_PROP_ATHENZ_CONF, + rootDir + "/conf/athenz/athenz.conf"); + String url = null; + try { + Path path = Paths.get(confFileName); + AthenzConfig conf = JSON.fromBytes(Files.readAllBytes(path), AthenzConfig.class); + url = conf.getZtsUrl(); + } catch (Exception ex) { + LOG.error("Unable to extract ZTS Url from {} exc: {}", + confFileName, ex.getMessage()); + } + + return url; + } + + void initClient(String url, Principal identity, String domainName, String serviceName) { + + if (url == null) { + ztsUrl = lookupZTSUrl(); + } else { + ztsUrl = url; + } + + /* verify if the url is ending with /zts/v1 and if it's + * not we'll automatically append it */ + + if (ztsUrl != null && !ztsUrl.isEmpty()) { + if (!ztsUrl.endsWith("/zts/v1")) { + if (ztsUrl.charAt(ztsUrl.length() - 1) != '/') { + ztsUrl += '/'; + } + ztsUrl += "zts/v1"; + } + } + + /* determine our read and connect timeouts */ + + int readTimeout = Integer.parseInt(System.getProperty(ZTS_CLIENT_PROP_READ_TIMEOUT, "30000")); + int connectTimeout = Integer.parseInt(System.getProperty(ZTS_CLIENT_PROP_CONNECT_TIMEOUT, "30000")); + String x509CertDNSName = System.getProperty(ZTS_CLIENT_PROP_X509CERT_DNS_NAME); + HostnameVerifier hostnameVerifier = null; + if (x509CertDNSName != null && !x509CertDNSName.isEmpty()) { + hostnameVerifier = new AWSHostNameVerifier(x509CertDNSName); + } + + ztsClient = new ZTSRDLGeneratedClient(ztsUrl, hostnameVerifier) + .setProperty(ClientProperties.CONNECT_TIMEOUT, connectTimeout) + .setProperty(ClientProperties.READ_TIMEOUT, readTimeout); + + principal = identity; + if (principal != null && principal.getAuthority() != null) { + domain = principal.getDomain(); + service = principal.getName(); + ztsClient.addCredentials(identity.getAuthority().getHeader(), identity.getCredentials()); + } else if (domainName != null && serviceName != null) { + domain = domainName; + service = serviceName; + siaClient = new SIAClient(); + } + } + + void setPrefetchInterval(long interval) { + prefetchInterval = interval; + } + + long getPrefetchInterval() { + return prefetchInterval; + } + + /** + * Returns the header name that the client needs to use to pass + * the received RoleToken to the Athenz protected service. + * @return HTTP header name + */ + public String getHeader() { + return ROLE_TOKEN_HEADER; + } + + /** + * Clear the principal identity set for the client. Unless a new principal is set + * using the addCredentials method, the client can only be used to requests data + * from the ZTS Server that doesn't require any authentication. + * @param identity Principal identity for authenticating requests + * @return self ZTSClient object + */ + public ZTSClient addCredentials(Principal identity) { + return addPrincipalCredentials(identity, true); + } + + /** + * Clear the principal identity set for the client. Unless a new principal is set + * using the addCredentials method, the client can only be used to requests data + * from the ZTS Server that doesn't require any authentication. + * @return self ZTSClient object + */ + public ZTSClient clearCredentials() { + + if (principal != null) { + ztsClient.addCredentials(principal.getAuthority().getHeader(), null); + principal = null; + } + return this; + } + + ZTSClient addPrincipalCredentials(Principal identity, boolean resetServiceDetails) { + + if (identity != null && identity.getAuthority() != null) { + ztsClient.addCredentials(identity.getAuthority().getHeader(), identity.getCredentials()); + } + + // if the client is adding new principal identity then we have to + // clear out the SIA client object reference so that we don't try + // to get a service token since we already have one given to us + + if (resetServiceDetails) { + siaClient = null; + } + + principal = identity; + return this; + } + + boolean sameCredentialsAsBefore(Principal svcPrincipal) { + + // if we don't have a principal or no credentials + // then the principal has changed + + if (principal == null) { + return false; + } + + String creds = principal.getCredentials(); + if (creds == null) { + return false; + } + + return creds.equals(svcPrincipal.getCredentials()); + } + + boolean updateServicePrincipal() { + + /* if we have a service principal then we need to keep updating + * our PrincipalToken otherwise it might expire. */ + + if (siaClient == null) { + return false; + } + + try { + Principal svcPrincipal = siaClient.getServicePrincipal(domain, service, + siaTokenMinExpiryTime, siaTokenMaxExpiryTime, false); + + // if the principal has the same credentials as before + // then we don't need to update anything + + if (sameCredentialsAsBefore(svcPrincipal)) { + return false; + } + + addPrincipalCredentials(svcPrincipal, false); + + } catch (IOException ex) { + + // we should log and throw an IllegalArgumentException otherwise the + // client doesn't know that something bad has happened - in this case + // illegal domain/service was passed to the constructor - + // and the ZTS Server just rejects the request with 401 + + String msg = "UpdateServicePrincipal: Unable to get PrincipalToken from SIA Server for " + + domain + "." + service + ": " + ex.getMessage(); + LOG.error(msg); + throw new IllegalArgumentException(msg); + } + + return true; + } + + /** + * Retrieve list of services that have been configured to run on the specified host + * @param host name of the host + * @return list of service names on success. ZTSClientException will be thrown in case of failure + */ + public HostServices getHostServices(String host) { + updateServicePrincipal(); + try { + return ztsClient.getHostServices(host); + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * For the specified requester(user/service) return the corresponding Role Token that + * includes the list of roles that the principal has access to in the specified domain. + * The client will automatically fulfill the request from the cache, if possible. + * The default minimum expiry time is 900 secs (15 mins). + * @param domainName name of the domain + * @return ZTS generated Role Token. ZTSClientException will be thrown in case of failure + */ + public RoleToken getRoleToken(String domainName) { + return getRoleToken(domainName, null, null, null, false, null); + } + + /** + * For the specified requester(user/service) return the corresponding Role Token that + * includes the list of roles that the principal has access to in the specified domain + * and filtered to include only those that end with the specified suffix. + * The client will automatically fulfill the request from the cache, if possible. + * The default minimum expiry time is 900 secs (15 mins). + * @param domainName name of the domain + * @param roleName only interested in roles with this name + * @return ZTS generated Role Token. ZTSClientException will be thrown in case of failure + */ + public RoleToken getRoleToken(String domainName, String roleName) { + if (roleName == null || roleName.isEmpty()) { + throw new IllegalArgumentException("RoleName cannot be null or empty"); + } + return getRoleToken(domainName, roleName, null, null, false, null); + } + + /** + * For the specified requester(user/service) return the corresponding Role Token that + * includes the list of roles that the principal has access to in the specified domain + * @param domainName name of the domain + * @param roleName (optional) only interested in roles with this name + * @param minExpiryTime (optional) specifies that the returned RoleToken must be + * at least valid (min/lower bound) for specified number of seconds, + * @param maxExpiryTime (optional) specifies that the returned RoleToken must be + * at most valid (max/upper bound) for specified number of seconds. + * @param ignoreCache ignore the cache and retrieve the token from ZTS Server + * @return ZTS generated Role Token. ZTSClientException will be thrown in case of failure + */ + public RoleToken getRoleToken(String domainName, String roleName, Integer minExpiryTime, + Integer maxExpiryTime, boolean ignoreCache) { + return getRoleToken(domainName, roleName, minExpiryTime, maxExpiryTime, + ignoreCache, null); + } + + /** + * For the specified requester(user/service) return the corresponding Role Token that + * includes the list of roles that the principal has access to in the specified domain + * @param domainName name of the domain + * @param roleName (optional) only interested in roles with this name + * @param minExpiryTime (optional) specifies that the returned RoleToken must be + * at least valid (min/lower bound) for specified number of seconds, + * @param maxExpiryTime (optional) specifies that the returned RoleToken must be + * at most valid (max/upper bound) for specified number of seconds. + * @param ignoreCache ignore the cache and retrieve the token from ZTS Server + * @param proxyForPrincipal (optional) this request is proxy for this principal + * @return ZTS generated Role Token. ZTSClientException will be thrown in case of failure + */ + public RoleToken getRoleToken(String domainName, String roleName, Integer minExpiryTime, + Integer maxExpiryTime, boolean ignoreCache, String proxyForPrincipal) { + + RoleToken roleToken = null; + + // first lookup in our cache to see if it can be satisfied + // only if we're not asked to ignore the cache + + String cacheKey = null; + if (!cacheDisabled) { + cacheKey = getRoleTokenCacheKey(domainName, roleName, proxyForPrincipal); + if (cacheKey != null && !ignoreCache) { + roleToken = lookupRoleTokenInCache(cacheKey, minExpiryTime, maxExpiryTime); + if (roleToken != null) { + return roleToken; + } + // start prefetch for this token if prefetch is enabled + if (enablePrefetch && prefetchAutoEnable) { + if (prefetchRoleToken(domainName, roleName, minExpiryTime, maxExpiryTime, proxyForPrincipal)) { + roleToken = lookupRoleTokenInCache(cacheKey, minExpiryTime, maxExpiryTime); + } + if (roleToken != null) { + return roleToken; + } + LOG.error("GetRoleToken: cache prefetch and lookup error"); + } + } + } + + // 2nd look in service providers + // + for (ZTSClientService provider: ZTS_TOKEN_PROVIDERS) { + if (LOG.isDebugEnabled()) { + LOG.debug("getRoleToken: found service provider=" + provider); + } + + // provider needs to know who the client is so we'll be passing + // the client's domain and service names as the first two fields + + roleToken = provider.fetchToken(domain, service, domainName, roleName, + minExpiryTime, maxExpiryTime, proxyForPrincipal); + if (roleToken != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("getRoleToken: service provider=" + provider + " returns token"); + } + return roleToken; + } + } + + // if no hit then we need to request a new token from ZTS + + updateServicePrincipal(); + try { + roleToken = ztsClient.getRoleToken(domainName, roleName, + minExpiryTime, maxExpiryTime, proxyForPrincipal); + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + + // need to add the token to our cache. If our principal was + // updated then we need to retrieve a new cache key + + if (!cacheDisabled) { + if (cacheKey == null) { + cacheKey = getRoleTokenCacheKey(domainName, roleName, proxyForPrincipal); + } + if (cacheKey != null) { + ROLE_TOKEN_CACHE.put(cacheKey, roleToken); + } + } + return roleToken; + } + + private static class RolePrefetchTask extends TimerTask { + + @Override + public void run() { + long currentTime = System.currentTimeMillis() / 1000; + FETCHER_LAST_RUN_AT.set(currentTime); + + if (LOG.isDebugEnabled()) { + LOG.debug("RolePrefetchTask: Fetching role token from the scheduled queue. Size=" + PREFETCH_SCHEDULED_ITEMS.size()); + } + if (PREFETCH_SCHEDULED_ITEMS.isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug("RolePrefetchTask: No items to fetch. Queue is empty"); + } + return; + } + + List toFetch = new ArrayList<>(PREFETCH_SCHEDULED_ITEMS.size()); + synchronized (PREFETCH_SCHEDULED_ITEMS) { + // if this item is to be fetched now, add it to collection + for (PrefetchRoleTokenScheduledItem item : PREFETCH_SCHEDULED_ITEMS) { + // see if item expires within next two minutes + long expiryTime = item.expiresAtUTC - (currentTime + FETCH_EPSILON + prefetchInterval); + if (LOG.isDebugEnabled()) { + LOG.debug("RolePrefetchTask: item=" + item.identityDomain + "." + item.identityName + + " domain=" + item.domainName + " suffix=" + item.roleName + + ": to be expired at " + expiryTime); + } + if (isExpiredToken(expiryTime, item.minDuration, item.maxDuration, item.tokenMinExpiryTime)) { + if (LOG.isDebugEnabled()) { + LOG.debug("RolePrefetchTask: item=" + item.identityDomain + "." + + item.identityName + " domain=" + item.domainName + + " roleName=" + item.roleName + ": expired. Fetch this item. " + expiryTime); + } + toFetch.add(item); + } + } + } + + // if toFetch is not empty, fetch those tokens, and add refreshed scheduled items back to the queue + if (!toFetch.isEmpty()) { + Set oldSvcLoaderCache = SVC_LOADER_CACHE_KEYS.get(); + Set newSvcLoaderCache = null; + // fetch items + for (PrefetchRoleTokenScheduledItem item : toFetch) { + // create ZTS Client for this particular item + try (ZTSClient itemZtsClient = new ZTSClient(item.identityDomain, item.identityName)) { + + if (item.siaClient != null) { + itemZtsClient.siaClient = item.siaClient; + } + if (item.ztsClient != null) { + itemZtsClient.ztsClient = item.ztsClient; + } + if (item.isRoleToken()) { + // check if this came from service provider + // + String key = itemZtsClient.getRoleTokenCacheKey(item.domainName, item.roleName, + item.proxyForPrincipal); + if (oldSvcLoaderCache.contains(key)) { + // if haven't gotten the new list of service + // loader tokens then get it now + if (newSvcLoaderCache == null) { + newSvcLoaderCache = loadSvcProviderTokens(); + } + // check if the key is in the new key set + // - if not, mark the item as invalid + if (!newSvcLoaderCache.contains(key)) { + item.invalid(true); + } + } else { + RoleToken token = itemZtsClient.getRoleToken(item.domainName, item.roleName, + item.minDuration, item.maxDuration, true, item.proxyForPrincipal); + // update the expire time + item.expiresAtUTC(token.getExpiryTime()); + } + } else { + AWSTemporaryCredentials awsCred = itemZtsClient.getAWSTemporaryCredentials(item.domainName, + item.roleName, true); + item.expiresAtUTC(awsCred.getExpiration().millis() / 1000); + } + } catch (Exception ex) { + // any exception should remove this item from fetch queue + item.invalid(true); + PREFETCH_SCHEDULED_ITEMS.remove(item); + LOG.error("RolePrefetchTask: Error while trying to prefetch token, msg=" + + ex.getMessage(), ex); + } + } + + // remove all invalid items. + toFetch.removeIf(p -> p.invalid); + + // now, add items back. + if (!toFetch.isEmpty()) { + synchronized (PREFETCH_SCHEDULED_ITEMS) { + // make sure there are no items of common + PREFETCH_SCHEDULED_ITEMS.removeAll(toFetch); + // add them back + PREFETCH_SCHEDULED_ITEMS.addAll(toFetch); + } + } + } + } + } + + // method useful for test purposes only + int getScheduledItemsSize() { + synchronized (PREFETCH_SCHEDULED_ITEMS) { + // ConcurrentLinkedQueue.size() method is typically not very useful in concurrent applications + return PREFETCH_SCHEDULED_ITEMS.size(); + } + } + + /** + * Pre-fetches role tokens so that the client does not take the hit of + * contacting ZTS Server for its first request (avg ~75ms). The client + * library will automatically try to keep the cache up to date such + * that the tokens are never expired and regular getRoleToken requests + * are fulfilled from the cache instead of contacting ZTS Server. + * @param domainName name of the domain + * @param roleName (optional) only interested in roles with this name + * @param minExpiryTime (optional) specifies that the returned RoleToken must be + * at least valid (min/lower bound) for specified number of seconds, + * @param maxExpiryTime (optional) specifies that the returned RoleToken must be + * at most valid (max/upper bound) for specified number of seconds. + * @return true if all is well, else false + */ + boolean prefetchRoleToken(String domainName, String roleName, + Integer minExpiryTime, Integer maxExpiryTime) { + + return prefetchRoleToken(domainName, roleName, minExpiryTime, maxExpiryTime, null); + } + + /** + * Pre-fetches role tokens so that the client does not take the hit of + * contacting ZTS Server for its first request (avg ~75ms). The client + * library will automatically try to keep the cache up to date such + * that the tokens are never expired and regular getRoleToken requests + * are fulfilled from the cache instead of contacting ZTS Server. + * @param domainName name of the domain + * @param roleName (optional) only interested in roles with this name + * @param minExpiryTime (optional) specifies that the returned RoleToken must be + * at least valid (min/lower bound) for specified number of seconds, + * @param maxExpiryTime (optional) specifies that the returned RoleToken must be + * at most valid (max/upper bound) for specified number of seconds. + * @param proxyForPrincipal (optional) request is proxy for this principal + * @return true if all is well, else false + */ + boolean prefetchRoleToken(String domainName, String roleName, + Integer minExpiryTime, Integer maxExpiryTime, String proxyForPrincipal) { + return prefetchToken(domainName, roleName, minExpiryTime, maxExpiryTime, proxyForPrincipal, true); + } + + boolean prefetchAwsCred(String domainName, String roleName, Integer minExpiryTime, Integer maxExpiryTime) { + return prefetchToken(domainName, roleName, minExpiryTime, maxExpiryTime, null, false); + } + + boolean prefetchToken(String domainName, String roleName, Integer minExpiryTime, + Integer maxExpiryTime, String proxyForPrincipal, boolean isRoleToken) { + + if (domainName == null || domainName.trim().isEmpty()) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, "Domain Name cannot be empty"); + } + + long expiryTimeUTC = 0; + if (isRoleToken) { + RoleToken token = getRoleToken(domainName, roleName, minExpiryTime, maxExpiryTime, true, proxyForPrincipal); + if (token == null) { + if (LOG.isWarnEnabled()) { + LOG.warn("PrefetchToken: No token fetchable using given params, domain=" + domainName + + ", roleSuffix=" + roleName); + } + return false; + } + expiryTimeUTC = token.getExpiryTime(); + } else { + AWSTemporaryCredentials awsCred = getAWSTemporaryCredentials(domainName, roleName, true); + if (awsCred == null) { + if (LOG.isWarnEnabled()) { + LOG.warn("PrefetchToken: No aws credential fetchable using given params, domain=" + domainName + + ", roleName=" + roleName); + } + return false; + } + expiryTimeUTC = awsCred.getExpiration().millis() / 1000; + } + + if (enablePrefetch == false || domain == null || domain.isEmpty() || service == null || service.isEmpty()) { + if (LOG.isWarnEnabled()) { + LOG.warn("PrefetchToken: Failure to setup ongoing prefetch of tokens. Both domain(" + + domain + ") and service(" + service + ") is required"); + } + return false; + } + PrefetchRoleTokenScheduledItem item = new PrefetchRoleTokenScheduledItem() + .isRoleToken(isRoleToken) + .domainName(domainName) + .roleName(roleName) + .proxyForPrincipal(proxyForPrincipal) + .minDuration(minExpiryTime) + .maxDuration(maxExpiryTime) + .expiresAtUTC(expiryTimeUTC) + .identityDomain(domain) + .identityName(service) + .tokenMinExpiryTime(ZTSClient.tokenMinExpiryTime) + .providedZTSUrl(this.ztsUrl) + .ztsClient(this.ztsClient) + .siaClient(this.siaClient); + + if (!PREFETCH_SCHEDULED_ITEMS.contains(item)) { + PREFETCH_SCHEDULED_ITEMS.add(item); + } else { + // contains item based on these 6 fields: + // domainName identityDomain identityName suffix trustDomain isRoleToken + // + // So need to remove and append since the new token expiry has changed + // .expiresAtUTC(token.getExpiryTime()) + // + PREFETCH_SCHEDULED_ITEMS.remove(item); + PREFETCH_SCHEDULED_ITEMS.add(item); + } + + if (FETCH_TIMER == null) { + synchronized (TIMER_LOCK) { + if (FETCH_TIMER == null) { + FETCH_TIMER = new Timer(); + // check the fetch items every prefetchInterval seconds. + FETCH_TIMER.schedule(new RolePrefetchTask(), 0, prefetchInterval * 1000); + } + } + } + + return true; + } + + String getRoleTokenCacheKey(String domainName, String roleName) { + return getRoleTokenCacheKey(domainName, roleName, null); + } + + String getRoleTokenCacheKey(String domainName, String roleName, String proxyForPrincipal) { + + // before we generate a cache key we need to have a valid domain + + if (domain == null) { + return null; + } + + StringBuilder cacheKey = new StringBuilder(256); + cacheKey.append("p="); + cacheKey.append(domain); + if (service != null) { + cacheKey.append(".").append(service); + } + + cacheKey.append(";d="); + cacheKey.append(domainName); + + if (roleName != null && !roleName.isEmpty()) { + cacheKey.append(";r="); + cacheKey.append(roleName); + } + if (proxyForPrincipal != null && !proxyForPrincipal.isEmpty()) { + cacheKey.append(";u="); + cacheKey.append(proxyForPrincipal); + } + + return cacheKey.toString(); + } + + boolean isExpiredToken(long expiryTime, Integer minExpiryTime, Integer maxExpiryTime) { + return isExpiredToken(expiryTime, minExpiryTime, maxExpiryTime, ZTSClient.tokenMinExpiryTime); + } + + static boolean isExpiredToken(long expiryTime, Integer minExpiryTime, Integer maxExpiryTime, int tokenMinExpiryTime) { + + // we'll first make sure if we're given both min and max expiry + // times then both conditions are satisfied + if (minExpiryTime != null && expiryTime < minExpiryTime) { + return true; + } + + if (maxExpiryTime != null && expiryTime > maxExpiryTime) { + return true; + } + + // if both limits were null then we need to make sure + // that our token is valid for based on our min configured value + + if (minExpiryTime == null && maxExpiryTime == null && expiryTime < tokenMinExpiryTime) { + return true; + } + + return false; + } + + RoleToken lookupRoleTokenInCache(String cacheKey, Integer minExpiryTime, Integer maxExpiryTime) { + + RoleToken roleToken = ROLE_TOKEN_CACHE.get(cacheKey); + if (roleToken == null) { + if (LOG.isInfoEnabled()) { + LOG.info("LookupRoleTokenInCache: cache-lookup key: " + cacheKey + " result: not found"); + } + return null; + } + + // before returning our cache hit we need to make sure it + // satisfies the time requirements as specified by the client + + long expiryTime = roleToken.getExpiryTime() - (System.currentTimeMillis() / 1000); + + if (isExpiredToken(expiryTime, minExpiryTime, maxExpiryTime, tokenMinExpiryTime)) { + + if (LOG.isInfoEnabled()) { + LOG.info("LookupRoleTokenInCache: role-cache-lookup key: " + cacheKey + " token-expiry: " + expiryTime + + " req-min-expiry: " + minExpiryTime + " req-max-expiry: " + maxExpiryTime + + " client-min-expiry: " + tokenMinExpiryTime + " result: expired"); + } + + ROLE_TOKEN_CACHE.remove(cacheKey); + return null; + } + + return roleToken; + } + + AWSTemporaryCredentials lookupAwsCredInCache(String cacheKey, Integer minExpiryTime, Integer maxExpiryTime) { + + AWSTemporaryCredentials awsCred = awsCredCache.get(cacheKey); + if (awsCred == null) { + if (LOG.isInfoEnabled()) { + LOG.info("LookupAwsCredInCache: aws-cache-lookup key: " + cacheKey + " result: not found"); + } + return null; + } + + // before returning our cache hit we need to make sure it + // satisfies the time requirements as specified by the client + + long expiryTime = awsCred.getExpiration().millis() - System.currentTimeMillis(); + expiryTime /= 1000; // expiry time is in seconds + + if (isExpiredToken(expiryTime, minExpiryTime, maxExpiryTime, tokenMinExpiryTime)) { + + if (LOG.isInfoEnabled()) { + LOG.info("LookupAwsCredInCache: aws-cache-lookup key: " + cacheKey + " token-expiry: " + expiryTime + + " req-min-expiry: " + minExpiryTime + " req-max-expiry: " + maxExpiryTime + + " client-min-expiry: " + tokenMinExpiryTime + " result: expired"); + } + + awsCredCache.remove(cacheKey); + return null; + } + + return awsCred; + } + + /** + * Retrieve the list of roles that the given principal has access to in the domain + * @param domainName name of the domain + * @param principal name of the principal + * @return RoleAccess object on success. ZTSClientException will be thrown in case of failure + */ + public RoleAccess getRoleAccess(String domainName, String principal) { + updateServicePrincipal(); + try { + return ztsClient.getRoleAccess(domainName, principal); + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getMessage()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the specified service object from a domain + * @param domainName name of the domain + * @param serviceName name of the service to be retrieved + * @return ServiceIdentity object on success. ZTSClientException will be thrown in case of failure + */ + public ServiceIdentity getServiceIdentity(String domainName, String serviceName) { + updateServicePrincipal(); + try { + return ztsClient.getServiceIdentity(domainName, serviceName); + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the specified public key from the given service object + * @param domainName name of the domain + * @param serviceName name of the service + * @param keyId the identifier of the public key to be retrieved + * @return PublicKeyEntry object or ZTSClientException will be thrown in case of failure + */ + public PublicKeyEntry getPublicKeyEntry(String domainName, String serviceName, String keyId) { + try { + return ztsClient.getPublicKeyEntry(domainName, serviceName, keyId); + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Retrieve the full list of services defined in a domain + * @param domainName name of the domain + * @return list of all service names on success. ZTSClientException will be thrown in case of failure + */ + public ServiceIdentityList getServiceIdentityList(String domainName) { + updateServicePrincipal(); + try { + return ztsClient.getServiceIdentityList(domainName); + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * For a given provider domain get a list of tenant domain names that the user is a member of + * @param providerDomainName name of the provider domain + * @param userName is the name of the user to search for in the tenant domains of the provider + * @param roleName is the name of the role to filter on when searching through the list of tenants with + * the specified role name. + * @param serviceName is the name of the service to filter on that the tenant has on-boarded to + * @return TenantDomains object which contains a list of tenant domain names for a given provider + * domain, that the user is a member of + */ + public TenantDomains getTenantDomains(String providerDomainName, String userName, + String roleName, String serviceName) { + updateServicePrincipal(); + try { + return ztsClient.getTenantDomains(providerDomainName, userName, roleName, serviceName); + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Request by a service to refresh its NToken. The original NToken must have been + * obtained by an authorized service by calling the postInstanceTenantRequest + * method. + * @param domain Name of the domain + * @param service Name of the service + * @param req InstanceRefreshRequest object for th request + * @return Identity object that includes a refreshed NToken for the service + */ + public Identity postInstanceRefreshRequest(String domain, String service, InstanceRefreshRequest req) { + updateServicePrincipal(); + try { + return ztsClient.postInstanceRefreshRequest(domain, service, req); + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * For a given domain and role return AWS temporary credentials + * @param domainName name of the domain + * @param roleName is the name of the role + * @return AWSTemporaryCredentials AWS credentials + */ + public AWSTemporaryCredentials getAWSTemporaryCredentials(String domainName, String roleName) { + + return getAWSTemporaryCredentials(domainName, roleName, false); + } + + public AWSTemporaryCredentials getAWSTemporaryCredentials(String domainName, String roleName, boolean ignoreCache) { + + // first lookup in our cache to see if it can be satisfied + // only if we're not asked to ignore the cache + + AWSTemporaryCredentials awsCred = null; + String cacheKey = getRoleTokenCacheKey(domainName, roleName, null); + if (cacheKey != null && !ignoreCache) { + awsCred = lookupAwsCredInCache(cacheKey, null, null); + if (awsCred != null) { + return awsCred; + } + // start prefetch for this token if prefetch is enabled + if (enablePrefetch && prefetchAutoEnable) { + if (prefetchAwsCred(domainName, roleName, null, null)) { + awsCred = lookupAwsCredInCache(cacheKey, null, null); + } + if (awsCred != null) { + return awsCred; + } + LOG.error("GetAWSTemporaryCredentials: cache prefetch and lookup error"); + } + } + + // if no hit then we need to request a new token from ZTS + + updateServicePrincipal(); + + try { + awsCred = ztsClient.getAWSTemporaryCredentials(domainName, roleName); + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + + // need to add the token to our cache. If our principal was + // updated then we need to retrieve a new cache key + + if (cacheKey == null) { + cacheKey = getRoleTokenCacheKey(domainName, roleName, null); + } + if (cacheKey != null) { + awsCredCache.put(cacheKey, awsCred); + } + return awsCred; + } + + /** + * Retrieve the list of all policies (not just names) from the ZTS Server that + * is signed with both ZTS's and ZMS's private keys. It will pass an option matchingTag + * so that ZTS can skip returning signed policies if no changes have taken + * place since that tag was issued. + * @param domainName name of the domain + * @param matchingTag name of the tag issued with last request + * @param responseHeaders contains the "tag" returned for modification + * time of the policies, map key = "tag", List should contain a single value + * @return list of policies signed by ZTS Server. ZTSClientException will be thrown in case of failure + */ + public DomainSignedPolicyData getDomainSignedPolicyData(String domainName, String matchingTag, + Map> responseHeaders) { + try { + DomainSignedPolicyData sp = ztsClient.getDomainSignedPolicyData(domainName, matchingTag, responseHeaders); + return sp; + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Verify if the given principal has access to the specified role in the + * domain or not. + * @param domainName name of the domain + * @param roleName name of the role + * @param principal name of the principal to check for + * @return Access object with grant true/false response. ZTSClientException will be thrown in case of failure + */ + public Access getAccess(String domainName, String roleName, String principal) { + updateServicePrincipal(); + try { + return ztsClient.getAccess(domainName, roleName, principal); + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + /** + * Caller may post set of domain metric attributes for monitoring and logging. + * ZTSClientException will be thrown in case of failure + * @param domainName name of the domain + * @param req list of domain metrics with their values + */ + public void postDomainMetrics(String domainName, DomainMetrics req) { + updateServicePrincipal(); + try { + ztsClient.postDomainMetrics(domainName, req); + } catch (ResourceException ex) { + throw new ZTSClientException(ex.getCode(), ex.getData()); + } catch (Exception ex) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, ex.getMessage()); + } + } + + static class PrefetchRoleTokenScheduledItem { + + boolean isRoleToken = true; + PrefetchRoleTokenScheduledItem isRoleToken(boolean isRole) { + isRoleToken = isRole; + return this; + } + boolean isRoleToken() { + return isRoleToken; + } + + String providedZTSUrl; + PrefetchRoleTokenScheduledItem providedZTSUrl(String u) { + providedZTSUrl = u; + return this; + } + + SIA siaClient; + PrefetchRoleTokenScheduledItem siaClient(SIA s) { + siaClient = s; + return this; + } + + ZTSRDLGeneratedClient ztsClient; + PrefetchRoleTokenScheduledItem ztsClient(ZTSRDLGeneratedClient z) { + ztsClient = z; + return this; + } + + boolean invalid; + PrefetchRoleTokenScheduledItem invalid(boolean i) { + invalid = i; + return this; + } + + String identityDomain; + PrefetchRoleTokenScheduledItem identityDomain(String d) { + identityDomain = d; + return this; + } + + String identityName; + PrefetchRoleTokenScheduledItem identityName(String d) { + identityName = d; + return this; + } + + String domainName; + PrefetchRoleTokenScheduledItem domainName(String d) { + domainName = d; + return this; + } + + String roleName; + PrefetchRoleTokenScheduledItem roleName(String s) { + roleName = s; + return this; + } + + String proxyForPrincipal; + PrefetchRoleTokenScheduledItem proxyForPrincipal(String u) { + proxyForPrincipal = u; + return this; + } + + Integer minDuration; + PrefetchRoleTokenScheduledItem minDuration(Integer min) { + minDuration = min; + return this; + } + + Integer maxDuration; + PrefetchRoleTokenScheduledItem maxDuration(Integer max) { + maxDuration = max; + return this; + } + + long expiresAtUTC; + PrefetchRoleTokenScheduledItem expiresAtUTC(long e) { + expiresAtUTC = e; + return this; + } + + int tokenMinExpiryTime; + PrefetchRoleTokenScheduledItem tokenMinExpiryTime(int t) { + tokenMinExpiryTime = t; + return this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((domainName == null) ? 0 : domainName.hashCode()); + result = prime * result + ((identityDomain == null) ? 0 : identityDomain.hashCode()); + result = prime * result + ((identityName == null) ? 0 : identityName.hashCode()); + result = prime * result + ((roleName == null) ? 0 : roleName.hashCode()); + result = prime * result + ((proxyForPrincipal == null) ? 0 : proxyForPrincipal.hashCode()); + result = prime * result + Boolean.hashCode(isRoleToken); + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrefetchRoleTokenScheduledItem other = (PrefetchRoleTokenScheduledItem) obj; + if (domainName == null) { + if (other.domainName != null) { + return false; + } + } else if (!domainName.equals(other.domainName)) { + return false; + } + if (identityDomain == null) { + if (other.identityDomain != null) { + return false; + } + } else if (!identityDomain.equals(other.identityDomain)) { + return false; + } + if (identityName == null) { + if (other.identityName != null) { + return false; + } + } else if (!identityName.equals(other.identityName)) { + return false; + } + if (roleName == null) { + if (other.roleName != null) { + return false; + } + } else if (!roleName.equals(other.roleName)) { + return false; + } + if (proxyForPrincipal == null) { + if (other.proxyForPrincipal != null) { + return false; + } + } else if (!proxyForPrincipal.equals(other.proxyForPrincipal)) { + return false; + } + return true; + } + + } + + public class AWSHostNameVerifier implements HostnameVerifier { + + String dnsHostname = null; + + public AWSHostNameVerifier(String hostname) { + dnsHostname = hostname; + } + + @Override + public boolean verify(String hostname, SSLSession session) { + + Certificate[] certs = null; + try { + certs = session.getPeerCertificates(); + } catch (SSLPeerUnverifiedException e) { + } + if (certs == null) { + return false; + } + + for (Certificate cert : certs) { + try { + X509Certificate x509Cert = (X509Certificate) cert; + if (matchDnsHostname(x509Cert.getSubjectAlternativeNames())) { + return true; + } + } catch (CertificateParsingException e) { + } + } + return false; + } + + boolean matchDnsHostname(Collection> altNames) { + + if (altNames == null) { + return false; + } + + // GeneralName ::= CHOICE { + // otherName [0] OtherName, + // rfc822Name [1] IA5String, + // dNSName [2] IA5String, + // x400Address [3] ORAddress, + // directoryName [4] Name, + // ediPartyName [5] EDIPartyName, + // uniformResourceIdentifier [6] IA5String, + // iPAddress [7] OCTET STRING, + // registeredID [8] OBJECT IDENTIFIER} + + for (@SuppressWarnings("rawtypes") List item : altNames) { + Integer type = (Integer) item.get(0); + if (type == 2) { + String dns = (String) item.get(1); + if (dnsHostname.equalsIgnoreCase(dns)) { + return true; + } + } + } + + return false; + } + } + + private static Set loadSvcProviderTokens() { + + // if have service loader implementations, then stuff role tokens into cache + // and keep track of these tokens so that they will get refreshed from + // service loader and not zts server + + Set cacheKeySet = new HashSet<>(); + for (ZTSClientService provider: ZTS_TOKEN_PROVIDERS) { + Collection descs = provider.loadTokens(); + if (descs == null) { + if (LOG.isInfoEnabled()) { + LOG.info("loadSvcProviderTokens: provider didn't return tokens: prov=" + provider); + } + continue; + } + for (ZTSClientService.RoleTokenDescriptor desc: descs) { + if (desc.signedToken != null) { + // stuff token in cache and record service loader key + String key = cacheSvcProvRoleToken(desc); + if (key != null) { + cacheKeySet.add(key); + } + } + } + } + + SVC_LOADER_CACHE_KEYS.set(cacheKeySet); + return cacheKeySet; + } + + /** + * stuff pre-loaded service token in cache. in this model an external + * service (proxy user) has retrieved the role tokens and added to the + * client cache so it can run without the need to contact zts server. + * in this model we're going to look at the principal field only and + * ignore the proxy field since the client doesn't need to know anything + * about that detail. + * + * start prefetch task to reload to prevent expiry + * return the cache key used + */ + static String cacheSvcProvRoleToken(ZTSClientService.RoleTokenDescriptor desc) { + + if (cacheDisabled) { + return null; + } + + com.yahoo.athenz.auth.token.RoleToken rt = new com.yahoo.athenz.auth.token.RoleToken(desc.getSignedToken()); + String domainName = rt.getDomain(); + String principalName = rt.getPrincipal(); + boolean completeRoleSet = rt.getDomainCompleteRoleSet(); + List roles = rt.getRoles(); + + // before doing anything else we need to see if we can cache this + // token - the requirement is either we have a full set or + // if it's not then we must have a single role in the list + + if (!completeRoleSet && roles.size() != 1) { + LOG.error("cacheSvcProvRoleToken: Unable to determine original rolename query: " + rt.getUnsignedToken()); + return null; + } + + // if the role token was for a complete set then we're not going + // to use the rolename field (it indicates that the original request + // was completed without the rolename field being specified) + + final String roleName = (completeRoleSet) ? null : rt.getRoles().get(0); + + // parse principalName for the tenant domain and service name + // we must have valid components otherwise we'll just + // ignore the token - you can't have a principal without + // valid domain and service names + + int index = principalName.lastIndexOf('.'); // ex: cities.burbank.mysvc + if (index == -1) { + LOG.error("cacheSvcProvRoleToken: Invalid principal in token: " + rt.getSignedToken()); + return null; + } + + final String tenantDomain = principalName.substring(0, index); + final String tenantService = principalName.substring(index + 1); + Long expiryTime = rt.getExpiryTime(); + + RoleToken roleToken = new RoleToken().setToken(desc.getSignedToken()).setExpiryTime(expiryTime); + + String key = null; + try (ZTSClient clt = new ZTSClient(tenantDomain, tenantService)) { + + key = clt.getRoleTokenCacheKey(domainName, roleName, null); + + if (LOG.isInfoEnabled()) { + LOG.info("cacheSvcProvRoleToken: cache-add key: " + key + " expiry: " + expiryTime); + } + + ROLE_TOKEN_CACHE.put(key, roleToken); + } + + // setup prefetch task + + Long expiryTimeUTC = roleToken.getExpiryTime(); + prefetchSvcProvTokens(tenantDomain, tenantService, domainName, + roleName, null, null, expiryTimeUTC, null); + + return key; + } + + static void prefetchSvcProvTokens(String domain, String service, String domainName, + String roleName, Integer minExpiryTime, Integer maxExpiryTime, + Long expiryTimeUTC, String proxyForPrincipal) { + + if (domainName == null || domainName.trim().isEmpty()) { + throw new ZTSClientException(ZTSClientException.BAD_REQUEST, "Domain Name cannot be empty"); + } + + PrefetchRoleTokenScheduledItem item = new PrefetchRoleTokenScheduledItem() + .isRoleToken(true) + .domainName(domainName) + .roleName(roleName) + .proxyForPrincipal(proxyForPrincipal) + .minDuration(minExpiryTime) + .maxDuration(maxExpiryTime) + .expiresAtUTC(expiryTimeUTC) + .identityDomain(domain) + .identityName(service) + .tokenMinExpiryTime(ZTSClient.tokenMinExpiryTime); + + if (PREFETCH_SCHEDULED_ITEMS.contains(item)) { + // contains item based on these 5 fields: + // domainName identityDomain identityName roleName proxyForProfile isRoleToken + // + // So need to remove and append since the new token expiry has changed + // .expiresAtUTC(token.getExpiryTime()) + // + PREFETCH_SCHEDULED_ITEMS.remove(item); + } + PREFETCH_SCHEDULED_ITEMS.add(item); + + if (FETCH_TIMER == null) { + synchronized (TIMER_LOCK) { + if (FETCH_TIMER == null) { + FETCH_TIMER = new Timer(); + // check the fetch items every prefetchInterval seconds. + FETCH_TIMER.schedule(new RolePrefetchTask(), 0, prefetchInterval * 1000); + } + } + } + } +} diff --git a/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientException.java b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientException.java new file mode 100644 index 00000000000..78b33111b4a --- /dev/null +++ b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientException.java @@ -0,0 +1,26 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +public class ZTSClientException extends ResourceException { + + private static final long serialVersionUID = -2191523509996056171L; + + public ZTSClientException(int code, Object data) { + super(code, data); + } +} diff --git a/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientService.java b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientService.java new file mode 100644 index 00000000000..0ba6e68b665 --- /dev/null +++ b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientService.java @@ -0,0 +1,83 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import java.util.Collection; + +/* + * Service Providers will implement this interface for special env needs + * whereby retrieving a role token from zts server may not be feasible. + * The provider implementation will need to be specified in the clients package + * in the file: META-INF/services/com.yahoo.athenz.zts.ZTSClientService + **/ +public interface ZTSClientService { + + /** + * Athenz token client cache entry + */ + public static class RoleTokenDescriptor { + + /** + * Construct the object with any needed params. Note there are no setters. + * @param signedToken signed role token from zts - required + */ + public RoleTokenDescriptor(String signedToken) { + this.signedToken = signedToken; + } + + public String getSignedToken() { + return signedToken; + } + + final String signedToken; + } + + /** + * ZTSClient calls to pre-load the Athens client token cache. + * @return collection of RoleTokenDescriptor objects that include + * the service retrieved role tokens. It can return either an + * empty set or null if there are no tokens to pre-load. + */ + default public Collection loadTokens() { + return null; + } + + /** + * ZTSClient will use this implementation to get a role token and avoid using the + * cache if a token is returned. If no token is returned, ZTSClient will process + * in the usual way - lookup in the cache (if not disabled) and then if not + * found contact ZTS Server directly to retrieve the role token. + * + * For the specified requester(user/service) return the corresponding Role Token that + * includes the list of roles that the principal has access to in the specified domain + * @param clientDomainName client's domain name that is requesting this role token + * @param clientServiceName client's service name that is requesting this role token + * @param domainName name of the domain to retrieve a role from + * @param roleName (optional) only interested in roles with this value + * @param minExpiryTime (optional) specifies that the returned RoleToken must be + * at least valid (min/lower bound) for specified number of seconds, + * @param maxExpiryTime (optional) specifies that the returned RoleToken must be + * at most valid (max/upper bound) for specified number of seconds. + * @param proxyForPrincipal (optional) this request is proxy for this principal + * @return ZTS generated Role Token. Must return null if not available/found. + */ + default public RoleToken fetchToken(String clientDomainName, String clientServiceName, + String domainName, String roleName, Integer minExpiryTime, Integer maxExpiryTime, + String proxyForPrincipal) { + return null; + } +} + diff --git a/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientTokenCacher.java b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientTokenCacher.java new file mode 100644 index 00000000000..dab64865363 --- /dev/null +++ b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSClientTokenCacher.java @@ -0,0 +1,61 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ZTSClientTokenCacher { + + private static final Logger LOG = LoggerFactory.getLogger(ZTSClientTokenCacher.class); + + public static void setRoleToken(String signedRoleToken, String roleName, String trustDomain) { + + // parse domain, roles, principalName, and expiry out of the token + + com.yahoo.athenz.auth.token.RoleToken rt = new com.yahoo.athenz.auth.token.RoleToken(signedRoleToken); + + String domainName = rt.getDomain(); + String principalName = rt.getPrincipal(); + + // parse principalName for the tenant domain and service name + // if we have an invalid principal name then we'll just skip + + String tenantDomain = null; + String serviceName = null; + int index = principalName.lastIndexOf('.'); // ex: cities.burbank.mysvc + if (index == -1) { + return; + } + + tenantDomain = principalName.substring(0, index); + serviceName = principalName.substring(index + 1); + Long expiryTime = rt.getExpiryTime(); + + RoleToken roleToken = new RoleToken().setToken(signedRoleToken).setExpiryTime(expiryTime); + + try (ZTSClient clt = new ZTSClient(tenantDomain, serviceName)) { + String key = clt.getRoleTokenCacheKey(domainName, roleName, null); + + if (LOG.isInfoEnabled()) { + LOG.info("ZTSTokenCache: cache-add key: " + key + " expiry: " + expiryTime); + } + + ZTSClient.ROLE_TOKEN_CACHE.put(key, roleToken); + } + } +} + diff --git a/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClient.java b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClient.java new file mode 100644 index 00000000000..79a9b6c342c --- /dev/null +++ b/clients/java/zts/src/main/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClient.java @@ -0,0 +1,333 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zts; + +import com.yahoo.rdl.*; +import javax.ws.rs.client.*; +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import javax.net.ssl.HostnameVerifier; + +public class ZTSRDLGeneratedClient { + Client client; + WebTarget base; + String credsHeader; + String credsToken; + + public ZTSRDLGeneratedClient(String url) { + client = ClientBuilder.newClient(); + base = client.target(url); + } + + public ZTSRDLGeneratedClient(String url, HostnameVerifier hostnameVerifier) { + client = ClientBuilder.newBuilder() + .hostnameVerifier(hostnameVerifier) + .build(); + base = client.target(url); + } + + public void close() { + client.close(); + } + + public ZTSRDLGeneratedClient setProperty(String name, Object value) { + client = client.property(name, value); + return this; + } + + public ZTSRDLGeneratedClient addCredentials(String header, String token) { + credsHeader = header; + credsToken = token; + return this; + } + + public ServiceIdentity getServiceIdentity(String domainName, String serviceName) { + WebTarget target = base.path("/domain/{domainName}/service/{serviceName}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("serviceName", serviceName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(ServiceIdentity.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public ServiceIdentityList getServiceIdentityList(String domainName) { + WebTarget target = base.path("/domain/{domainName}/service") + .resolveTemplate("domainName", domainName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(ServiceIdentityList.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public PublicKeyEntry getPublicKeyEntry(String domainName, String serviceName, String keyId) { + WebTarget target = base.path("/domain/{domainName}/service/{serviceName}/publickey/{keyId}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("serviceName", serviceName) + .resolveTemplate("keyId", keyId); + Invocation.Builder invocationBuilder = target.request("application/json"); + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(PublicKeyEntry.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public HostServices getHostServices(String host) { + WebTarget target = base.path("/host/{host}/services") + .resolveTemplate("host", host); + Invocation.Builder invocationBuilder = target.request("application/json"); + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(HostServices.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public DomainSignedPolicyData getDomainSignedPolicyData(String domainName, String matchingTag, java.util.Map> headers) { + WebTarget target = base.path("/domain/{domainName}/signed_policy_data") + .resolveTemplate("domainName", domainName); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (matchingTag != null) { + invocationBuilder = invocationBuilder.header("If-None-Match", matchingTag); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + case 304: + if (headers != null) { + headers.put("tag", java.util.Arrays.asList((String)response.getHeaders().getFirst("ETag"))); + } + if (code == 304) { + return null; + } + return response.readEntity(DomainSignedPolicyData.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public RoleToken getRoleToken(String domainName, String role, Integer minExpiryTime, Integer maxExpiryTime, String proxyForPrincipal) { + WebTarget target = base.path("/domain/{domainName}/token") + .resolveTemplate("domainName", domainName); + if (role != null) { + target = target.queryParam("role", role); + } + if (minExpiryTime != null) { + target = target.queryParam("minExpiryTime", minExpiryTime); + } + if (maxExpiryTime != null) { + target = target.queryParam("maxExpiryTime", maxExpiryTime); + } + if (proxyForPrincipal != null) { + target = target.queryParam("proxyForPrincipal", proxyForPrincipal); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(RoleToken.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Access getAccess(String domainName, String roleName, String principal) { + WebTarget target = base.path("/access/domain/{domainName}/role/{roleName}/principal/{principal}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("roleName", roleName) + .resolveTemplate("principal", principal); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Access.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public RoleAccess getRoleAccess(String domainName, String principal) { + WebTarget target = base.path("/access/domain/{domainName}/principal/{principal}") + .resolveTemplate("domainName", domainName) + .resolveTemplate("principal", principal); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(RoleAccess.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TenantDomains getTenantDomains(String providerDomainName, String userName, String roleName, String serviceName) { + WebTarget target = base.path("/providerdomain/{providerDomainName}/user/{userName}") + .resolveTemplate("providerDomainName", providerDomainName) + .resolveTemplate("userName", userName); + if (roleName != null) { + target = target.queryParam("roleName", roleName); + } + if (serviceName != null) { + target = target.queryParam("serviceName", serviceName); + } + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(TenantDomains.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Identity postInstanceInformation(InstanceInformation info) { + WebTarget target = base.path("/instance"); + Invocation.Builder invocationBuilder = target.request("application/json"); + Response response = invocationBuilder.post(javax.ws.rs.client.Entity.entity(info, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Identity.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Identity postInstanceRefreshRequest(String domain, String service, InstanceRefreshRequest req) { + WebTarget target = base.path("/instance/{domain}/{service}/refresh") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.post(javax.ws.rs.client.Entity.entity(req, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Identity.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Identity postAWSInstanceInformation(AWSInstanceInformation info) { + WebTarget target = base.path("/aws/instance"); + Invocation.Builder invocationBuilder = target.request("application/json"); + Response response = invocationBuilder.post(javax.ws.rs.client.Entity.entity(info, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Identity.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Identity postAWSCertificateRequest(String domain, String service, AWSCertificateRequest req) { + WebTarget target = base.path("/aws/instance/{domain}/{service}/refresh") + .resolveTemplate("domain", domain) + .resolveTemplate("service", service); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.post(javax.ws.rs.client.Entity.entity(req, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Identity.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public AWSTemporaryCredentials getAWSTemporaryCredentials(String domainName, String role) { + WebTarget target = base.path("/domain/{domainName}/role/{role}/creds") + .resolveTemplate("domainName", domainName) + .resolveTemplate("role", role); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(AWSTemporaryCredentials.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public DomainMetrics postDomainMetrics(String domainName, DomainMetrics req) { + WebTarget target = base.path("/metrics/{domainName}") + .resolveTemplate("domainName", domainName); + Invocation.Builder invocationBuilder = target.request("application/json"); + Response response = invocationBuilder.post(javax.ws.rs.client.Entity.entity(req, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(DomainMetrics.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + +} diff --git a/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.core.JsonFactory b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.core.JsonFactory new file mode 100644 index 00000000000..8fdbde1f214 --- /dev/null +++ b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.core.JsonFactory @@ -0,0 +1 @@ +athenz.shade.zts.com.fasterxml.jackson.core.JsonFactory diff --git a/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.core.ObjectCodec b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.core.ObjectCodec new file mode 100644 index 00000000000..8e5c5d1129a --- /dev/null +++ b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.core.ObjectCodec @@ -0,0 +1 @@ +athenz.shade.zts.com.fasterxml.jackson.databind.ObjectMapper diff --git a/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.databind.Module b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.databind.Module new file mode 100644 index 00000000000..31b2acaf4da --- /dev/null +++ b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.com.fasterxml.jackson.databind.Module @@ -0,0 +1 @@ +athenz.shade.zts.com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule diff --git a/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.client.ClientBuilder b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.client.ClientBuilder new file mode 100644 index 00000000000..8cce2def714 --- /dev/null +++ b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.client.ClientBuilder @@ -0,0 +1 @@ +athenz.shade.zts.org.glassfish.jersey.client.JerseyClientBuilder diff --git a/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.MessageBodyReader b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.MessageBodyReader new file mode 100644 index 00000000000..24ee78c9ea1 --- /dev/null +++ b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.MessageBodyReader @@ -0,0 +1 @@ +athenz.shade.zts.com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider diff --git a/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.MessageBodyWriter b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.MessageBodyWriter new file mode 100644 index 00000000000..24ee78c9ea1 --- /dev/null +++ b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.MessageBodyWriter @@ -0,0 +1 @@ +athenz.shade.zts.com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider diff --git a/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.RuntimeDelegate b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.RuntimeDelegate new file mode 100644 index 00000000000..ca9090ee738 --- /dev/null +++ b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.javax.ws.rs.ext.RuntimeDelegate @@ -0,0 +1 @@ +athenz.shade.zts.org.glassfish.jersey.internal.RuntimeDelegateImpl diff --git a/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.org.glassfish.hk2.extension.ServiceLocatorGenerator b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.org.glassfish.hk2.extension.ServiceLocatorGenerator new file mode 100644 index 00000000000..15b62923421 --- /dev/null +++ b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.org.glassfish.hk2.extension.ServiceLocatorGenerator @@ -0,0 +1 @@ +athenz.shade.zts.org.jvnet.hk2.external.generator.ServiceLocatorGeneratorImpl diff --git a/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.org.glassfish.jersey.internal.spi.AutoDiscoverable b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.org.glassfish.jersey.internal.spi.AutoDiscoverable new file mode 100644 index 00000000000..ecae8c20568 --- /dev/null +++ b/clients/java/zts/src/main/resources/META-INF/services/athenz.shade.zts.org.glassfish.jersey.internal.spi.AutoDiscoverable @@ -0,0 +1,2 @@ +athenz.shade.zts.org.glassfish.jersey.jackson.internal.JacksonAutoDiscoverable +athenz.shade.zts.org.glassfish.jersey.logging.LoggingFeatureAutoDiscoverable diff --git a/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientMock.java b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientMock.java new file mode 100644 index 00000000000..de5bcf00a7e --- /dev/null +++ b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientMock.java @@ -0,0 +1,326 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.yahoo.rdl.Timestamp; + +public class ZTSClientMock extends ZTSRDLGeneratedClient implements java.io.Closeable { + + public ZTSClientMock() { + super("http://localhost:4080/"); + } + + public ZTSClientMock(String url) { + super(url); + } + + private long sleepInterval = 60; + private int expiryTime = 2400; + private String roleName = null; + private String policyName = null; + private List tenantDomains = null; + + Map credsMap = new HashMap<>(); + + private Map lastRoleTokenFetchedTime = new HashMap(); + + static String getKey(String domain, String roleName, String proxyForPrincipal) { + return domain + "-" + roleName + "-" + proxyForPrincipal; + } + + long getLastRoleTokenFetchedTime(String domain, String roleName) { + return getLastRoleTokenFetchedTime(domain, roleName, null); + } + + long getLastRoleTokenFetchedTime(String domain, String roleName, String proxyForPrincipal) { + String key = getKey(domain, roleName, proxyForPrincipal); + if (lastRoleTokenFetchedTime.containsKey(key)) { + return lastRoleTokenFetchedTime.get(key); + } + return -1; + } + + public void setTestSleepInterval(long intervalSecs) { + sleepInterval = intervalSecs; + } + + @Override + public HostServices getHostServices(String hostName) { + if (hostName == "not.exist.host") { + throw new ResourceException(404, "hostname not found"); + } + + HostServices hostServices = new HostServices().setHost(hostName) + .setNames(Arrays.asList("service1", "service2")); + return hostServices; + } + + @Override + public PublicKeyEntry getPublicKeyEntry(String domainName, String serviceName, + String keyId) { + if (domainName == "invalid.domain") { + throw new ResourceException(404, "invalid domain"); + } + + PublicKeyEntry publicKeyEntry = new PublicKeyEntry().setId(keyId).setKey("test-key"); + return publicKeyEntry; + } + + @Override + public RoleAccess getRoleAccess(String domainName, String principal) { + if (domainName.equals("exc")) { + throw new ResourceException(400, "Invalid request"); + } else if (domainName.equals("unknown")) { + throw new ResourceException(404, "Unknown domain"); + } + RoleAccess roleAccess = new RoleAccess(); + ArrayList roles = new ArrayList<>(); + roles.add("role1"); + roles.add("role2"); + roleAccess.setRoles(roles); + return roleAccess; + } + + @Override + public RoleToken getRoleToken(String domainName, String rName, + Integer minExpiryTime, Integer maxExpiryTime, String proxyForPrincipal) { + + if (rName != null && !roleName.endsWith(rName)) { + throw new ResourceException(403, "No access to any roles"); + } + + // calculate expiry time based on test domain name + long expireWindow = expiryTime; + if (domainName.equals("providerdomain")) { + expireWindow = sleepInterval + 10; + } + + List roles = new ArrayList<>(); + roles.add(roleName); + com.yahoo.athenz.auth.token.RoleToken token = + new com.yahoo.athenz.auth.token.RoleToken.Builder("Z1", domainName, roles) + .expirationWindow(expireWindow).keyId("0").proxyUser(proxyForPrincipal).build(); + RoleToken roleToken = new RoleToken(); + roleToken.setToken(token.getUnsignedToken()); + roleToken.setExpiryTime(token.getExpiryTime()); + String key = getKey(domainName, rName, proxyForPrincipal); + long lastUpdatedTime = System.currentTimeMillis(); + lastRoleTokenFetchedTime.put(key, lastUpdatedTime); + return roleToken; + } + + @Override + public ServiceIdentity getServiceIdentity(String domainName, String serviceName) { + if (domainName == "unknown.domain") { + throw new ResourceException(404, "Domain not found"); + } + + ServiceIdentity serviceIdentity = new ServiceIdentity().setName(serviceName); + + return serviceIdentity; + } + + @Override + public ServiceIdentityList getServiceIdentityList(String domainName) { + if (domainName == "unknown.domain") { + throw new ResourceException(404, "Domain not found"); + } + + ServiceIdentityList serviceIdentityList = new ServiceIdentityList().setNames(Arrays.asList("storage")); + + return serviceIdentityList; + } + + public void setAwsCreds(Timestamp expiration, String domainName, String roleName) { + String key = domainName + ":" + roleName; + setAwsCreds(expiration, domainName, roleName, key + "session", key + "secret", key + "keyid"); + } + + public void setAwsCreds(Timestamp expiration, String domainName, String roleName, + String sessToken, String secretKey, String accessKeyId) { + + AWSTemporaryCredentials awsCreds = new AWSTemporaryCredentials(); + String key = domainName + ":" + roleName; + awsCreds.setExpiration(expiration).setSessionToken(sessToken). + setSecretAccessKey(secretKey).setAccessKeyId(accessKeyId); + + credsMap.put(key, awsCreds); + } + + @Override + public AWSTemporaryCredentials getAWSTemporaryCredentials(String domainName, String roleName) { + + if (credsMap.isEmpty()) { + throw new ZTSClientException(ZTSClientException.NOT_FOUND, "role is not assumed"); + } + String key = domainName + ":" + roleName; + AWSTemporaryCredentials creds = credsMap.get(key); + if (creds == null) { + return null; + } + + // calculate expiry time based on test domain name + long expireWindow = expiryTime; + if (domainName.equals("providerdomain")) { + expireWindow = sleepInterval + 10; + } + long expiration = System.currentTimeMillis() + (expireWindow * 1000); + String sessToken = Long.toString(expiration); + String oldSessToken = creds.getSessionToken(); + String keyid = creds.getAccessKeyId(); + String secret = creds.getSecretAccessKey(); + + creds = new AWSTemporaryCredentials(); + creds.setExpiration(Timestamp.fromMillis(expiration)); + creds.setAccessKeyId(keyid); + creds.setSecretAccessKey(secret); + + String []split = oldSessToken.split(":"); + creds.setSessionToken(split[0] + ":" + sessToken); + + key = getKey(domainName, roleName, null); + credsMap.put(key, creds); + long lastUpdatedTime = System.currentTimeMillis(); + lastRoleTokenFetchedTime.put(key, lastUpdatedTime); + + return creds; + } + + @Override + public DomainSignedPolicyData getDomainSignedPolicyData(String domainName, String matchingTag, + Map> responseHeaders) { + if (policyName == null) { + return null; + } else if (domainName == null) { + throw new ResourceException(400, "Invalid request"); + } + + Policy policy = new Policy(); + policy.setName(policyName); + + List policyList = new ArrayList<>(); + policyList.add(policy); + + PolicyData policyData = new PolicyData(); + policyData.setDomain(domainName); + policyData.setPolicies(policyList); + + SignedPolicyData signedPolicyData = new SignedPolicyData(); + signedPolicyData.setZmsSignature("zmsSignature"); + signedPolicyData.setZmsKeyId("0"); + signedPolicyData.setPolicyData(policyData); + signedPolicyData.setExpires(Timestamp.fromMillis(System.currentTimeMillis())); + signedPolicyData.setModified(Timestamp.fromMillis(System.currentTimeMillis())); + + DomainSignedPolicyData domSignedPolicyData = new DomainSignedPolicyData(); + domSignedPolicyData.setKeyId("0"); + domSignedPolicyData.setSignature("signature"); + domSignedPolicyData.setSignedPolicyData(signedPolicyData); + + return domSignedPolicyData; + } + + @Override + public TenantDomains getTenantDomains(String providerDomainName, String userName, + String roleName, String serviceName) { + if (providerDomainName.equals("exc")) { + throw new ResourceException(400, "Invalid request"); + } else if (providerDomainName.equals("unknown")) { + throw new ResourceException(404, "Unknown domain"); + } + + TenantDomains doms = new TenantDomains(); + doms.setTenantDomainNames(tenantDomains); + return doms; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public String getPolicyName() { + return policyName; + } + + public void setPolicyName(String policyName) { + this.policyName = policyName; + } + + public int getExpiryTime() { + return expiryTime; + } + + public void setExpiryTime(int expiryTime) { + this.expiryTime = expiryTime; + } + + public void setTenantDomains(List tenantDomains) { + this.tenantDomains = tenantDomains; + } + + @Override + public Identity postAWSCertificateRequest(String domain, String service, AWSCertificateRequest req) { + return null; + } + + @Override + public Identity postInstanceInformation(InstanceInformation info) { + return null; + } + + @Override + public Identity postAWSInstanceInformation(AWSInstanceInformation info) { + return null; + } + + @Override + public Identity postInstanceRefreshRequest(String domain, String service, InstanceRefreshRequest req) { + if (domain.equals("exc")) { + throw new ResourceException(400, "Invalid request"); + } + Identity identity = new Identity().setName(domain + "." + service) + .setServiceToken("v=S1;d=" + domain + ";n=" + service + ";k=zts.dev;z=zts;o=athenz.svc;t=1234;e=1235;s=sig"); + return identity; + } + + @Override + public Access getAccess(String domainName, String roleName, String principal) { + if (domainName.equals("exc")) { + throw new ResourceException(400, "Invalid request"); + } + Access access = new Access(); + access.setGranted(roleName.equals("match")); + return access; + } + + @Override + public DomainMetrics postDomainMetrics(String domainName, DomainMetrics req) { + if (domainName.equals("exc")) { + throw new ResourceException(400, "Invalid request"); + } + return null; + } +} diff --git a/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientServiceProvider.java b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientServiceProvider.java new file mode 100644 index 00000000000..ab8b2bbf429 --- /dev/null +++ b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientServiceProvider.java @@ -0,0 +1,58 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +public class ZTSClientServiceProvider implements ZTSClientService { + + static RoleToken roleToken; + static String domainName; + static String roleName; + static String trustDomain; + static String proxyForPrincipal; + + @Override + public RoleToken fetchToken(String domain, String seervice, String domName, + String rName, Integer minExpiryTime, Integer maxExpiryTime, String proxy) { + + System.out.println("ZTSClientServiceProvider:fetchToken: domain=" + domName + + " role=" + roleName + " proxy=" + proxy); + + if (domainName == null || !domainName.equals(domName)) { + return null; + } + + if (roleName != null && !roleName.equals(rName)) { + return null; + } + + if (proxyForPrincipal != null && !proxyForPrincipal.equals(proxy)) { + return null; + } + + System.out.println("ZTSClientServiceProvider:fetchToken: return token for domain=" + domName + + " role=" + rName + " proxy=" + proxy); + return roleToken; + } + + public static void setToken(RoleToken rToken, String domName, String rName, String proxy) { + + roleToken = rToken; + domainName = domName; + roleName = rName; + proxyForPrincipal = proxy; + } +} + diff --git a/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientTest.java b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientTest.java new file mode 100644 index 00000000000..e88732d5efc --- /dev/null +++ b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSClientTest.java @@ -0,0 +1,2153 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; + +import org.mockito.Mockito; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.PrincipalAuthority; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.sia.impl.SIAClient; +import com.yahoo.rdl.Timestamp; + +public class ZTSClientTest { + + final private Authority PRINCIPAL_AUTHORITY = new PrincipalAuthority(); + + @BeforeClass + public void init() { + System.setProperty(ZTSClient.ZTS_CLIENT_PROP_PREFETCH_SLEEP_INTERVAL, "5"); + System.setProperty(ZTSClient.ZTS_CLIENT_PROP_PREFETCH_AUTO_ENABLE, "false"); + } + + @AfterMethod + public void cleanup() { + System.clearProperty(ZTSClient.ZTS_CLIENT_PROP_ATHENZ_CONF); + System.clearProperty(ZTSClient.ZTS_CLIENT_PROP_TOKEN_MIN_EXPIRY_TIME); + ZTSClient.initConfigValues(); + } + + @BeforeMethod + public void setup() { + System.setProperty(ZTSClient.ZTS_CLIENT_PROP_ATHENZ_CONF, "src/test/resources/athenz.conf"); + } + + @Test + public void testGetHeader() { + + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + assertEquals(client.getHeader(), "Athenz-Role-Auth"); + client.close(); + } + + @Test + public void testLookupZTSUrl() { + + System.setProperty(ZTSClient.ZTS_CLIENT_PROP_ATHENZ_CONF, "src/test/resources/athenz.conf"); + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + assertEquals(client.lookupZTSUrl(), "https://dev.zts.athenzcompany.com:4443/"); + System.clearProperty(ZTSClient.ZTS_CLIENT_PROP_ATHENZ_CONF); + client.close(); + } + + @Test + public void testLookupZTSUrlInvalidFile() { + System.setProperty(ZTSClient.ZTS_CLIENT_PROP_ATHENZ_CONF, "src/test/resources/athenz_invaild.conf"); + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + assertNull(client.lookupZTSUrl()); + client.close(); + } + + @Test + public void testIsExpiredTokenSmallerThanMin() { + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + assertTrue(client.isExpiredToken(100, 200, null)); + client.close(); + } + + @Test + public void testIsExpiredTokenBiggerThanMax() { + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + assertTrue(client.isExpiredToken(500, null, 300)); + assertTrue(client.isExpiredToken(500, 200, 300)); + client.close(); + } + + @Test + public void testIsExpiredTokenAtLeastOneLimitIsNotNull() { + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + assertFalse(client.isExpiredToken(500, null, 600)); + assertFalse(client.isExpiredToken(500, 200, null)); + assertFalse(client.isExpiredToken(500, 200, 501)); + client.close(); + } + + @Test + public void testIsExpiredTokenAtLeastBothLimitsNullSmallerThanMin() { + System.setProperty(ZTSClient.ZTS_CLIENT_PROP_TOKEN_MIN_EXPIRY_TIME, "600"); + ZTSClient.initConfigValues(); + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + assertTrue(client.isExpiredToken(500, null, null)); + client.close(); + } + + @Test + public void testIsExpiredTokenAtLeastBothLimitsNullBiggerThanMin() { + System.setProperty(ZTSClient.ZTS_CLIENT_PROP_TOKEN_MIN_EXPIRY_TIME, "400"); + ZTSClient.initConfigValues(); + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + System.out.println("test:isexpiredtoken: expiry=500"); + assertFalse(client.isExpiredToken(500, null, null)); + client.close(); + } + + @Test + public void testGetRoleTokenCacheKeyNullPrincipal() { + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + assertNull(client.getRoleTokenCacheKey("coretech", null, "TrustDomain")); + client.close(); + } + + @Test + public void testGetRoleTokenCacheKeyNullPrincipalCredentials() { + Principal principal = SimplePrincipal.create("user_domain", "user", (String) null, PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + assertNotNull(client.getRoleTokenCacheKey("coretech", null, "TrustDomain")); + client.close(); + } + + @Test + public void testGetRoleTokenCacheKeyNullRole() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + assertEquals(client.getRoleTokenCacheKey("coretech", null, null), "p=user_domain.user;d=coretech"); + client.close(); + } + + @Test + public void testGetRoleTokenCacheKeyEmptyRole() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + assertEquals(client.getRoleTokenCacheKey("coretech", "", null), "p=user_domain.user;d=coretech"); + client.close(); + } + + @Test + public void testGetRoleTokenCacheKey() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + assertEquals(client.getRoleTokenCacheKey("coretech", "Role1", "proxy"), + "p=user_domain.user;d=coretech;r=Role1;u=proxy"); + client.close(); + } + + @Test + public void testLookupAwsCredInCacheNotPresent() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + + String cacheKey = "p=auth_creds;d=coretech;r=Role1"; + assertNull(client.lookupAwsCredInCache(cacheKey, null, null)); + client.close(); + } + + @SuppressWarnings("static-access") + @Test + public void testLookupAwsCredInCacheExpired() { + + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + + String cacheKey = "p=auth_creds;d=coretech;r=Role1"; + AWSTemporaryCredentials awsCred = new AWSTemporaryCredentials().setAccessKeyId("accesskey") + .setExpiration(Timestamp.fromMillis((System.currentTimeMillis() / 1000) + 1000L)) + .setSecretAccessKey("secretkey").setSessionToken("sesstoken"); + client.awsCredCache.put(cacheKey, awsCred); + + assertNull(client.lookupAwsCredInCache(cacheKey, 3000, 4000)); + assertNull(client.lookupAwsCredInCache(cacheKey, 500, 800)); + + client.awsCredCache.clear(); + client.close(); + } + + @SuppressWarnings("static-access") + @Test + public void testLookupAwsCredInCacheSecondClient() { + + // test cache with ZTSClient created using a principal object + // + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient ztsClient = new ZTSClient("http://localhost:4080/", principal); + String accessKey = "accesskey"; + String secretKey = "secretkey"; + String sessToken = "sesstoken"; + AWSTemporaryCredentials awsCred = new AWSTemporaryCredentials() + .setExpiration(Timestamp.fromMillis(System.currentTimeMillis() + 3500000L)) + .setAccessKeyId(accessKey).setSecretAccessKey(secretKey).setSessionToken(sessToken); + String cacheKey = ztsClient.getRoleTokenCacheKey("coretech", "Role1", null); + ztsClient.awsCredCache.put(cacheKey, awsCred); + assertEquals(cacheKey, "p=user_domain.user;d=coretech;r=Role1"); + AWSTemporaryCredentials cred = ztsClient.lookupAwsCredInCache(cacheKey, 3000, 4000); + assertTrue(cred.getAccessKeyId().contains(accessKey)); + assertEquals(cred.getSecretAccessKey(), secretKey); + assertEquals(cred.getSessionToken(), sessToken); + + ztsClient.close(); + + // rest of tests use ZTSClient object created using domain name and service parameters + + ZTSClient client = new ZTSClient("mytenantdomain", "myservice"); + + String cacheKey1 = client.getRoleTokenCacheKey("mydomain", "Role1", null); + client.awsCredCache.put(cacheKey1, awsCred); + assertNotNull(client.lookupAwsCredInCache(cacheKey1, 3000, 4000)); + + // add new aws cred for caching + + String cacheKey2 = client.getRoleTokenCacheKey("mydomain", "admin", null); + AWSTemporaryCredentials awsCredNoTrustDomain = new AWSTemporaryCredentials() + .setExpiration(Timestamp.fromMillis(System.currentTimeMillis() + 3500000L)) + .setAccessKeyId("notrustdomaccesskey") + .setSecretAccessKey(secretKey).setSessionToken(sessToken); + client.awsCredCache.put(cacheKey2, awsCredNoTrustDomain); + assertEquals(cacheKey2, "p=mytenantdomain.myservice;d=mydomain;r=admin"); + assertNotNull(client.lookupAwsCredInCache(cacheKey2, 3000, 4000)); + + // now let's get another client - same domain and service as first one + // + ZTSClient client1 = new ZTSClient("mytenantdomain", "myservice"); + assertNotNull(client1.lookupAwsCredInCache(cacheKey, 3000, 4000)); + assertEquals(client1.lookupAwsCredInCache(cacheKey2, 3000, 4000).getAccessKeyId(), "notrustdomaccesskey"); + + // now let's get yet another client - different domain and service + // + ZTSClient client2 = new ZTSClient("mytenantdomain2", "myservice2"); + + // cache still contains aws creds for the following keys + assertNotNull(client2.lookupAwsCredInCache(cacheKey, 3000, 4000)); + + // add new role token to cache using new domain=mydomain2 and new tenant domain=mytenantdomain2 and new service=myservice2 + String cacheKeyNewDomain = client2.getRoleTokenCacheKey("mydomain2", "admin", null); + AWSTemporaryCredentials awsCredNewDomain = new AWSTemporaryCredentials() + .setExpiration(Timestamp.fromMillis(System.currentTimeMillis() + 3500000L)) + .setAccessKeyId("newdomaccesskey") + .setSecretAccessKey(secretKey).setSessionToken(sessToken); + client.awsCredCache.put(cacheKeyNewDomain, awsCredNewDomain); + assertEquals(cacheKeyNewDomain, "p=mytenantdomain2.myservice2;d=mydomain2;r=admin"); + assertEquals(client2.lookupAwsCredInCache(cacheKeyNewDomain, 3000, 4000).getAccessKeyId(), "newdomaccesskey"); + + // set aws cred without specifying role for the key + // + String cacheKeyNoRole = client2.getRoleTokenCacheKey("mydomain2", null, null); + AWSTemporaryCredentials awsCredNoRole = new AWSTemporaryCredentials() + .setExpiration(Timestamp.fromMillis(System.currentTimeMillis() + 3500000L)) + .setAccessKeyId("noroleaccesskey") + .setSecretAccessKey(secretKey).setSessionToken(sessToken); + client.awsCredCache.put(cacheKeyNoRole, awsCredNoRole); + assertEquals(cacheKeyNoRole, "p=mytenantdomain2.myservice2;d=mydomain2"); + assertEquals(client2.lookupAwsCredInCache(cacheKeyNoRole, 3000, 4000).getAccessKeyId(), "noroleaccesskey"); + + // now let's get yet another client - specify domain but no service + // + ZTSClient client3 = new ZTSClient("mytenantdomain3", (String) null); + + // cache still contains role tokens for the following keys + assertNotNull(client3.lookupAwsCredInCache(cacheKey, 3000, 4000)); + + // token principal field has no service so in sync with ZTSClient + String cacheKeyNoSvc = client3.getRoleTokenCacheKey("mydomain3", null, null); + assertNull(cacheKeyNoSvc); + + client.ROLE_TOKEN_CACHE.clear(); + client.close(); + client1.close(); + client2.close(); + client3.close(); + } + + @Test + public void testLookupRoleTokenInCacheNotPresent() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + + String cacheKey = "p=auth_creds;d=coretech;r=Role1"; + assertNull(client.lookupRoleTokenInCache(cacheKey, null, null)); + client.close(); + } + + + @SuppressWarnings("static-access") + @Test + public void testLookupRoleTokenInCacheExpired() { + + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + + String cacheKey = "p=auth_creds;d=coretech;r=Role1"; + RoleToken roleToken = new RoleToken().setToken("role_token").setExpiryTime((System.currentTimeMillis() / 1000) + 1000L); + client.ROLE_TOKEN_CACHE.put(cacheKey, roleToken); + + assertNull(client.lookupRoleTokenInCache(cacheKey, 3000, 4000)); + assertNull(client.lookupRoleTokenInCache(cacheKey, 500, 800)); + + client.ROLE_TOKEN_CACHE.clear(); + client.close(); + } + + @SuppressWarnings("static-access") + @Test + public void testLookupRoleTokenInCache() { + + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + + String cacheKey = "p=user_domain.user;d=coretech;r=Role1"; + RoleToken roleToken = new RoleToken().setToken("role_token").setExpiryTime((System.currentTimeMillis() / 1000) + 3500L); + client.ROLE_TOKEN_CACHE.put(cacheKey, roleToken); + + assertNotNull(client.lookupRoleTokenInCache(cacheKey, 3000, 4000)); + + Long expiryTime = roleToken.getExpiryTime(); + String token = "v=Z1;d=mydomain;r=admin;p=user_domain.user;h=localhost;a=f10bc905071a72d1;t=1448045776;e=" + expiryTime.toString() + ";k=0;i=10.11.12.13;s=pujvQuvaLa2jgE3k24bCw5Hm7AP9dUQkmkwNfX2bPhVXyhdRkOlbttF4exJm9V571sJXid6vsihgopCdxqW_qA--"; + ZTSClientTokenCacher.setRoleToken(token, "admin", null); + cacheKey = client.getRoleTokenCacheKey("mydomain", "admin", null); + assertEquals(cacheKey, "p=user_domain.user;d=mydomain;r=admin"); + assertNotNull(client.lookupRoleTokenInCache(cacheKey, 3000, 4000)); + + client.ROLE_TOKEN_CACHE.clear(); + client.close(); + } + + @SuppressWarnings("static-access") + @Test + public void testLookupRoleTokenInCacheSecondClient() { + + // test cache with ZTSClient created using a principal object + // + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient ztsClient = new ZTSClient("http://localhost:4080/", principal); + RoleToken roleToken = new RoleToken().setToken("role_token").setExpiryTime((System.currentTimeMillis() / 1000) + 3500L); + String coreTechToken = "v=Z1;d=coretech;r=admin;p=user_domain.user;h=localhost;a=f10bc905071a72d1;t=1448045776;e=" + roleToken.getExpiryTime() + ";k=0;i=10.11.12.13;s=pujvQuvaLa2jgE3k24bCw5Hm7AP9dUQkmkwNfX2bPhVXyhdRkOlbttF4exJm9V571sJXid6vsihgopCdxqW_qA--"; + ZTSClientTokenCacher.setRoleToken(coreTechToken, "Role1", null); + String cacheKey = ztsClient.getRoleTokenCacheKey("coretech", "Role1", null); + assertEquals(cacheKey, "p=user_domain.user;d=coretech;r=Role1"); + assertEquals(ztsClient.lookupRoleTokenInCache(cacheKey, 3000, 4000).getToken(), coreTechToken); + ztsClient.close(); + + // rest of tests use ZTSClient object created using domain name and service parameters + + ZTSClient client = new ZTSClient("mytenantdomain", "myservice"); + + String cacheKeyRole1 = client.getRoleTokenCacheKey("mydomain", "Role1", null); + client.ROLE_TOKEN_CACHE.put(cacheKeyRole1, roleToken); + + assertNotNull(client.lookupRoleTokenInCache(cacheKeyRole1, 3000, 4000)); + + // add new role token to the cache + // + Long expiryTime = roleToken.getExpiryTime(); + String token = "v=Z1;d=mydomain;r=admin;p=mytenantdomain.myservice;h=localhost;a=f10bc905071a72d1;t=1448045776;e=" + expiryTime.toString() + ";k=0;i=10.11.12.13;s=pujvQuvaLa2jgE3k24bCw5Hm7AP9dUQkmkwNfX2bPhVXyhdRkOlbttF4exJm9V571sJXid6vsihgopCdxqW_qA--"; + ZTSClientTokenCacher.setRoleToken(token, "admin", null); + String cacherKeyCacher = client.getRoleTokenCacheKey("mydomain", "admin", null); + assertEquals(cacherKeyCacher, "p=mytenantdomain.myservice;d=mydomain;r=admin"); + assertNotNull(client.lookupRoleTokenInCache(cacherKeyCacher, 3000, 4000)); + + // now let's get another client - same domain and service as first one + // + ZTSClient client1 = new ZTSClient("mytenantdomain", "myservice"); + assertNotNull(client1.lookupRoleTokenInCache(cacheKey, 3000, 4000)); + assertNotNull(client1.lookupRoleTokenInCache(cacherKeyCacher, 3000, 4000)); + + // now let's get yet another client - different domain and service + // + ZTSClient client2 = new ZTSClient("mytenantdomain2", "myservice2"); + + // cache still contains role tokens for the following keys + assertNotNull(client2.lookupRoleTokenInCache(cacheKey, 3000, 4000)); + assertNotNull(client2.lookupRoleTokenInCache(cacherKeyCacher, 3000, 4000)); + + // add new role token to cache using new domain=mydomain2 and new tenant domain=mytenantdomain2 and new service=myservice2 + String token2 = "v=Z1;d=mydomain2;r=admin;p=mytenantdomain2.myservice2;h=localhost;a=f10bc905071a72d1;t=1448045776;e=" + expiryTime.toString() + ";k=0;i=10.11.12.13;s=pujvQuvaLa2jgE3k24bCw5Hm7AP9dUQkmkwNfX2bPhVXyhdRkOlbttF4exJm9V571sJXid6vsihgopCdxqW_qA--"; + ZTSClientTokenCacher.setRoleToken(token2, "admin", null); + String cacheKeyNewDomain = client2.getRoleTokenCacheKey("mydomain2", "admin", null); + assertEquals(cacheKeyNewDomain, "p=mytenantdomain2.myservice2;d=mydomain2;r=admin"); + assertEquals(client2.lookupRoleTokenInCache(cacheKeyNewDomain, 3000, 4000).getToken(), token2); + + // set role token without specifying role for the key + // + ZTSClientTokenCacher.setRoleToken(token2, null, null); + String cacheKeyNoRole = client2.getRoleTokenCacheKey("mydomain2", null, null); + assertEquals(cacheKeyNoRole, "p=mytenantdomain2.myservice2;d=mydomain2"); + assertEquals(client2.lookupRoleTokenInCache(cacheKeyNoRole, 3000, 4000).getToken(), token2); + + // now let's get yet another client - specify domain but no service + // + ZTSClient client3 = new ZTSClient("mytenantdomain3", (String) null); + + // cache still contains role tokens for the following keys + assertNotNull(client3.lookupRoleTokenInCache(cacheKey, 3000, 4000)); + assertNotNull(client3.lookupRoleTokenInCache(cacherKeyCacher, 3000, 4000)); + + String cacheKeyNoSvc = client3.getRoleTokenCacheKey("mydomain3", null, null); + assertNull(cacheKeyNoSvc); + + client.ROLE_TOKEN_CACHE.clear(); + client.close(); + client1.close(); + client2.close(); + client3.close(); + } + + @SuppressWarnings("static-access") + @Test + public void testLookupRoleTokenServiceProvider() { + + String domName = "svcdomtest"; + Long expiryTime = (System.currentTimeMillis() / 1000) + 3500L; + String token = "v=Z1;d=" + domName + ";r=admin;p=sports.hockey;h=localhost;a=f10bc905071a72d1;t=1448045776;e=" + expiryTime.toString() + + ";k=0;i=10.11.12.13;s=pujvQuvaLa2jgE3k24bCw5Hm7AP9dUQkmkwNfX2bPhVXyhdRkOlbttF4exJm9V571sJXid6vsihgopCdxqW_qA--"; + RoleToken roleToken = new RoleToken().setToken(token).setExpiryTime((System.currentTimeMillis() / 1000) + 3500L); + + java.util.ServiceLoader providers = java.util.ServiceLoader.load(ZTSClientService.class); + for (ZTSClientService provider: providers) { + if (provider instanceof ZTSClientServiceProvider) { + ZTSClientServiceProvider sprov = (ZTSClientServiceProvider) provider; + sprov.setToken(roleToken, domName, null, null); + roleToken = null; + break; + } + } + assertNull(roleToken); // means it found the test service provider + + // now get role token for that domain + Authority principalAuthority = new PrincipalAuthority(); + Principal principal = SimplePrincipal.create("sports", "hockey", "v=S1;d=sports;n=hockey;s=sig", principalAuthority); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setRoleName("role1"); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.setZTSRDLGeneratedClient(ztsClientMock); + + // purposely ignoring cache so 1st thing it will do is check in the providers + RoleToken rToken = client.getRoleToken(domName, null, null, null, true, null); + assertNotNull(rToken); + + // not in cache + String cacheKey = client.getRoleTokenCacheKey(domName, null, null); + rToken = client.lookupRoleTokenInCache(cacheKey, null, null); + assertNull(rToken); + + // dont ignore cache so 1st thing it will do is check in the cache + // before it checks the providers + rToken = client.getRoleToken(domName, null, null, null, false, null); + assertNotNull(rToken); + + client.close(); + } + + @Test + public void testUpdateServicePrincipalSIAClientNull() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + assertFalse(client.updateServicePrincipal()); + client.close(); + } + + @SuppressWarnings("unchecked") + @Test + public void testUpdateServicePrincipalException() throws IOException { + SIAClient siaClient = Mockito.mock(SIAClient.class); + Mockito.when(siaClient.getServicePrincipal(Mockito.eq("iaas.athenz"), + Mockito.eq("ci"), Mockito.anyInt(), Mockito.anyInt(), + Mockito.eq(false))).thenThrow(IOException.class); + + ZTSClient client = new ZTSClient("http://localhost:4080/", + "iaas.athenz", "ci"); + client.setSIAClient(siaClient); + try { + client.updateServicePrincipal(); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("iaas.athenz.ci")); + } + client.close(); + } + + @Test + public void testSameCredentialsAsBeforeNullPrincipal() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + assertFalse(client.sameCredentialsAsBefore(principal)); + client.close(); + } + + @Test + public void testSameCredentialsAsBeforePrincipalNoCreds() { + + Principal principal = SimplePrincipal.create("user_domain", "user", (String) null, PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + Principal newPrincipal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + assertFalse(client.sameCredentialsAsBefore(newPrincipal)); + client.close(); + } + + @Test + public void testSameCredentialsAsBeforePrincipalDiffCreds1() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds2", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + Principal newPrincipal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + assertFalse(client.sameCredentialsAsBefore(newPrincipal)); + client.close(); + } + + @Test + public void testSameCredentialsAsBeforePrincipalDiffCreds2() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + Principal newPrincipal = SimplePrincipal.create("user_domain", "user", "auth_creds2", PRINCIPAL_AUTHORITY); + assertFalse(client.sameCredentialsAsBefore(newPrincipal)); + client.close(); + } + + @Test + public void testSameCredentialsAsBeforePrincipalSameCreds() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + Principal newPrincipal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + assertTrue(client.sameCredentialsAsBefore(newPrincipal)); + client.close(); + } + + @Test + public void testAddandClearCredentials() { + + // add credential + ZTSClient client = new ZTSClient("coretech", "storage"); + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + client.addCredentials(principal); + assertNotNull(client.principal); + assertNull(client.siaClient); + + // clear credential + client.clearCredentials(); + assertNull(client.principal); + + client.close(); + } + + @Test + public void testAddPrincipalCredentialsNoSIAReset() { + + ZTSClient client = new ZTSClient("coretech", "storage"); + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + client.addPrincipalCredentials(principal, false); + assertNotNull(client); + assertNotNull(client.siaClient); + client.close(); + } + + @Test + public void testAddPrincipalCredentialsSIAReset() { + + ZTSClient client = new ZTSClient("coretech", "storage"); + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + client.addPrincipalCredentials(principal, true); + assertNotNull(client); + assertNull(client.siaClient); + client.close(); + } + + @Test + public void testConstructorPrincipal() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient(principal); + assertNotNull(client); + assertNotNull(client.ztsClient); + assertNull(client.siaClient); + assertEquals(client.principal, principal); + } + + @Test + public void testConstructorPrincipalAndUrl() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080/", principal); + assertNotNull(client); + assertNotNull(client.ztsClient); + assertNull(client.siaClient); + assertEquals(client.principal, principal); + assertEquals(client.getZTSUrl(), "http://localhost:4080/zts/v1"); + } + + @Test + public void testConstructorServiceWithClients() { + + ZTSClient client = new ZTSClient("http://localhost:4080/", "coretech", "storage"); + assertNotNull(client); + assertNotNull(client.siaClient); + assertNull(client.principal); + } + + @Test + public void testGetZTSUrlWithTrailingSlash() { + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + assertEquals(client.getZTSUrl(), "http://localhost:4080/zts/v1"); + client.close(); + } + + @Test + public void testGetZTSUrlWithoutTrailingSlash() { + ZTSClient client = new ZTSClient("http://localhost:4080/", (Principal) null); + assertEquals(client.getZTSUrl(), "http://localhost:4080/zts/v1"); + client.close(); + } + + @Test + public void testClientInvalidPort() { + + ZTSClient client = new ZTSClient("http://localhost:11080/", (Principal) null); + + try { + client.getRoleToken("coretech"); + fail(); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 400); + } + + client.close(); + } + + @Test + public void testGetRoleToken() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setRoleName("role1"); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.setZTSRDLGeneratedClient(ztsClientMock); + + RoleToken roleToken = client.getRoleToken("coretech"); + assertNotNull(roleToken); + + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getDomain(), "coretech"); + assertEquals(1, token.getRoles().size()); + assertTrue(token.getRoles().contains("role1")); + + // now we're going to get a token again and this time we should get back + // from our cache thus the same exact one + + RoleToken roleToken2 = client.getRoleToken("coretech"); + assertTrue(roleToken2.getToken().equals(roleToken.getToken())); + + // now we're going to use the full API to request the token with ignoring from the cache + // and we should get back a new token + + roleToken2 = client.getRoleToken("coretech", null, null, null, true, null); + assertFalse(roleToken2.getToken().equals(roleToken.getToken())); + client.close(); + } + + @Test + public void testPrefetchRoleTokenShouldNotCallServer() throws Exception { + System.out.println("testPrefetchRoleTokenShouldNotCallServer"); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setRoleName("role1"); + long intervalSecs = Integer.parseInt(System.getProperty(ZTSClient.ZTS_CLIENT_PROP_PREFETCH_SLEEP_INTERVAL, "5")); + ztsClientMock.setTestSleepInterval(intervalSecs); + + final Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + SIAClient siaClient = Mockito.mock(SIAClient.class); + Mockito.when(siaClient.getServicePrincipal(Mockito.anyString(), + Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), + Mockito.eq(false))).thenReturn(principal); + + ZTSClient client = new ZTSClient("http://localhost:4080/", "user_domain", "user"); + client.setZTSRDLGeneratedClient(ztsClientMock); + client.setSIAClient(siaClient); + + String domain1 = "coretech"; + String domain2 = "providerdomain"; + + // initially, roleToken was never fetched. + assertTrue(ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null) < 0); + + // initialize the prefetch token process. + client.prefetchRoleToken(domain1, null, null, null, null); + int scheduledItemsSize = client.getScheduledItemsSize(); + + // make sure only unique items are in the queue + client.prefetchRoleToken(domain1, null, null, null, null); + int scheduledItemsSize2 = client.getScheduledItemsSize(); + assertEquals(scheduledItemsSize, scheduledItemsSize2); + + RoleToken roleToken1 = client.getRoleToken(domain1); + assertTrue(roleToken1 != null); + long rt1Expiry = roleToken1.getExpiryTime(); + + client.prefetchRoleToken(domain2, null, null, null, null); + assertEquals(client.getScheduledItemsSize(), scheduledItemsSize + 1); + + RoleToken roleToken2 = client.getRoleToken(domain2); + assertTrue(roleToken2 != null); + long rt2Expiry = roleToken2.getExpiryTime(); + System.out.println("testPrefetchRoleTokenShouldNotCallServer: roleToken2:domain=" + domain2 + " expires at " + rt2Expiry + " curtime_secs=" + (System.currentTimeMillis() / 1000)); + + System.out.println("testPrefetchRoleTokenShouldNotCallServer: sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchRoleTokenShouldNotCallServer: nap over so what happened"); + + assertEquals(client.getScheduledItemsSize(), scheduledItemsSize + 1); + long lastTimerTriggered1 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + long lastTokenFetchedTime1 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null); + assertTrue(lastTokenFetchedTime1 > 0); + + roleToken2 = client.getRoleToken(domain2); + long rt2Expiry2 = roleToken2.getExpiryTime(); + System.out.println("testPrefetchRoleTokenShouldNotCallServer: roleToken2:domain=" + domain2 + " expires at " + rt2Expiry2 + " curtime_secs=" + (System.currentTimeMillis() / 1000)); + assertTrue(rt2Expiry2 > rt2Expiry); // this token was refreshed + + // wait a few seconds, and see subsequent fetch happened. + System.out.println("testPrefetchRoleTokenShouldNotCallServer: again sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchRoleTokenShouldNotCallServer: again nap over so what happened"); + + RoleToken roleToken3 = client.getRoleToken(domain2); + long rt2Expiry3 = roleToken3.getExpiryTime(); + System.out.println("testPrefetchRoleTokenShouldNotCallServer: roleToken3:domain=" + domain2 + " expires at " + rt2Expiry3); + assertTrue(rt2Expiry3 > rt2Expiry2); // this token was refreshed + + long lastTokenFetchedTime2 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null); + long lastTokenFetchedTime3 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null); + long lastTimerTriggered2 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + // Since token should be good for 2 hrs, lastTokenFetchedTime1 & 2 & 3 all should be the same + // because token is not expired yet. + assertEquals(lastTokenFetchedTime1, lastTokenFetchedTime2); + assertEquals(lastTokenFetchedTime3, lastTokenFetchedTime2); + + // token should be identical since didnt get refreshed + RoleToken roleToken1b = client.getRoleToken(domain1); + long rt1bExpiry = roleToken1b.getExpiryTime(); + assertEquals(rt1Expiry, rt1bExpiry); + assertEquals(roleToken1.getToken(), roleToken1b.getToken()); + + // But, make sure the Timer actually triggered. + assertTrue(lastTimerTriggered1 > 0); + assertTrue(lastTimerTriggered2 > 0); + assertNotEquals(lastTimerTriggered1, lastTimerTriggered2); + assertTrue(lastTimerTriggered2 > lastTimerTriggered1); + + client.removePrefetcher(); + client.close(); + } + + @Test + public void testPrefetchRoleTokenWithUserDataShouldNotCallServer() throws Exception { + System.out.println("testPrefetchRoleTokenShouldNotCallServer"); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setRoleName("role1"); + long intervalSecs = Integer.parseInt(System.getProperty(ZTSClient.ZTS_CLIENT_PROP_PREFETCH_SLEEP_INTERVAL, "5")); + ztsClientMock.setTestSleepInterval(intervalSecs); + + final Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + SIAClient siaClient = Mockito.mock(SIAClient.class); + Mockito.when(siaClient.getServicePrincipal(Mockito.anyString(), + Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), + Mockito.eq(false))).thenReturn(principal); + + ZTSClient client = new ZTSClient("http://localhost:4080/", "user_domain", "user"); + client.setZTSRDLGeneratedClient(ztsClientMock); + client.setSIAClient(siaClient); + + String domain1 = "coretech"; + String domain2 = "providerdomain"; + + // initially, roleToken was never fetched. + assertTrue(ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, "user_domain.userdata1") < 0); + + // initialize the prefetch token process. + client.prefetchRoleToken(domain1, null, null, null, "user_domain.userdata1"); + int scheduledItemsSize = client.getScheduledItemsSize(); + + // make sure only unique items are in the queue + client.prefetchRoleToken(domain1, null, null, null, "user_domain.userdata1"); + int scheduledItemsSize2 = client.getScheduledItemsSize(); + assertEquals(scheduledItemsSize, scheduledItemsSize2); + + RoleToken roleToken1 = client.getRoleToken(domain1, null, null, null, false, "user_domain.userdata1"); + assertTrue(roleToken1 != null); + long rt1Expiry = roleToken1.getExpiryTime(); + + client.prefetchRoleToken(domain2, null, null, null, "user_domain.userdata2"); + assertEquals(client.getScheduledItemsSize(), scheduledItemsSize + 1); + + RoleToken roleToken2 = client.getRoleToken(domain2, null, null, null, false, "user_domain.userdata2"); + assertTrue(roleToken2 != null); + long rt2Expiry = roleToken2.getExpiryTime(); + System.out.println("testPrefetchRoleTokenShouldNotCallServer: roleToken2:domain=" + domain2 + " expires at " + rt2Expiry + " curtime_secs=" + (System.currentTimeMillis() / 1000)); + + System.out.println("testPrefetchRoleTokenShouldNotCallServer: sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchRoleTokenShouldNotCallServer: nap over so what happened"); + + assertEquals(client.getScheduledItemsSize(), scheduledItemsSize + 1); + long lastTimerTriggered1 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + long lastTokenFetchedTime1 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, "user_domain.userdata1"); + assertTrue(lastTokenFetchedTime1 > 0); + + roleToken2 = client.getRoleToken(domain2, null, null, null, false, "user_domain.userdata2"); + long rt2Expiry2 = roleToken2.getExpiryTime(); + System.out.println("testPrefetchRoleTokenShouldNotCallServer: roleToken2:domain=" + domain2 + " expires at " + rt2Expiry2 + " curtime_secs=" + (System.currentTimeMillis() / 1000)); + assertTrue(rt2Expiry2 > rt2Expiry); // this token was refreshed + + // wait a few seconds, and see subsequent fetch happened. + System.out.println("testPrefetchRoleTokenShouldNotCallServer: again sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchRoleTokenShouldNotCallServer: again nap over so what happened"); + + RoleToken roleToken3 = client.getRoleToken(domain2, null, null, null, false, "user_domain.userdata2"); + assertTrue(roleToken3.getToken().contains(";proxy=user_domain.userdata2;")); + long rt2Expiry3 = roleToken3.getExpiryTime(); + System.out.println("testPrefetchRoleTokenShouldNotCallServer: roleToken3:domain=" + domain2 + " expires at " + rt2Expiry3); + assertTrue(rt2Expiry3 > rt2Expiry2); // this token was refreshed + + long lastTokenFetchedTime2 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, "user_domain.userdata1"); + long lastTokenFetchedTime3 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, "user_domain.userdata1"); + long lastTimerTriggered2 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + // Since token should be good for 2 hrs, lastTokenFetchedTime1 & 2 & 3 all should be the same + // because token is not expired yet. + assertEquals(lastTokenFetchedTime1, lastTokenFetchedTime2); + assertEquals(lastTokenFetchedTime3, lastTokenFetchedTime2); + + // token should be identical since didnt get refreshed + RoleToken roleToken1b = client.getRoleToken(domain1, null, null, null, false, "user_domain.userdata1"); + assertTrue(roleToken1b.getToken().contains(";proxy=user_domain.userdata1;")); + long rt1bExpiry = roleToken1b.getExpiryTime(); + assertEquals(rt1Expiry, rt1bExpiry); + assertEquals(roleToken1.getToken(), roleToken1b.getToken()); + + // But, make sure the Timer actually triggered. + assertTrue(lastTimerTriggered1 > 0); + assertTrue(lastTimerTriggered2 > 0); + assertNotEquals(lastTimerTriggered1, lastTimerTriggered2); + assertTrue(lastTimerTriggered2 > lastTimerTriggered1); + + client.removePrefetcher(); + client.close(); + } + + @Test + public void testPrefetchAwsCredShouldNotCallServer() throws Exception { + System.out.println("testPrefetchAwsCredShouldNotCallServer"); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setRoleName("role1"); + long intervalSecs = Integer.parseInt(System.getProperty(ZTSClient.ZTS_CLIENT_PROP_PREFETCH_SLEEP_INTERVAL, "5")); + ztsClientMock.setTestSleepInterval(intervalSecs); + + final Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + SIAClient siaClient = Mockito.mock(SIAClient.class); + Mockito.when(siaClient.getServicePrincipal(Mockito.anyString(), + Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), + Mockito.eq(false))).thenReturn(principal); + + ZTSClient client = new ZTSClient("http://localhost:4080/", "user_domain", "user"); + client.setZTSRDLGeneratedClient(ztsClientMock); + client.setSIAClient(siaClient); + + String domain1 = "coretech"; + String domain2 = "providerdomain"; + ztsClientMock.setAwsCreds(Timestamp.fromCurrentTime(), domain1, "role1"); + ztsClientMock.setAwsCreds(Timestamp.fromCurrentTime(), domain2, "role1"); + ztsClientMock.setAwsCreds(Timestamp.fromCurrentTime(), domain2, "role2"); + + // initially, roleToken was never fetched. + assertTrue(ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null) < 0); + + // initialize the prefetch token process. + client.prefetchAwsCred(domain1, "role1", null, null); + int scheduledItemsSize = client.getScheduledItemsSize(); + + // make sure only unique items are in the queue + client.prefetchAwsCred(domain1, "role1", null, null); + int scheduledItemsSize2 = client.getScheduledItemsSize(); + assertEquals(scheduledItemsSize, scheduledItemsSize2); + + AWSTemporaryCredentials awsCred1 = client.getAWSTemporaryCredentials(domain1, "role1"); + assertTrue(awsCred1 != null); + long rt1Expiry = awsCred1.getExpiration().millis(); + + client.prefetchAwsCred(domain2, "role1", null, null); + assertEquals(client.getScheduledItemsSize(), scheduledItemsSize + 1); + + AWSTemporaryCredentials awsCred2 = client.getAWSTemporaryCredentials(domain2, "role1"); + assertTrue(awsCred2 != null); + long rt2Expiry = awsCred2.getExpiration().millis(); + System.out.println("testPrefetchAwsCredShouldNotCallServer: awsCred2:domain=" + domain2 + " expires at " + rt2Expiry + " curtime_millis=" + System.currentTimeMillis()); + + System.out.println("testPrefetchAwsCredShouldNotCallServer: sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchAwsCredShouldNotCallServer: nap over so what happened"); + + assertEquals(client.getScheduledItemsSize(), scheduledItemsSize + 1); + long lastTimerTriggered1 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + long lastTokenFetchedTime1 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null); + assertTrue(lastTokenFetchedTime1 > 0); + + awsCred2 = client.getAWSTemporaryCredentials(domain2, "role1"); + long rt2Expiry2 = awsCred2.getExpiration().millis(); + System.out.println("testPrefetchAwsCredShouldNotCallServer: awsCred2:domain=" + domain2 + " expires at " + rt2Expiry2 + " curtime_millis=" + System.currentTimeMillis()); + assertTrue(rt2Expiry2 > rt2Expiry); // this token was refreshed + + // wait a few seconds, and see subsequent fetch happened. + System.out.println("testPrefetchAwsCredShouldNotCallServer: again sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchAwsCredShouldNotCallServer: again nap over so what happened"); + + AWSTemporaryCredentials awsCred3 = client.getAWSTemporaryCredentials(domain2, "role1"); + long rt2Expiry3 = awsCred3.getExpiration().millis(); + System.out.println("testPrefetchAwsCredShouldNotCallServer: awsCred3:domain=" + domain2 + " expires at " + rt2Expiry3); + assertTrue(rt2Expiry3 > rt2Expiry2); // this token was refreshed + + long lastTokenFetchedTime2 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null); + long lastTokenFetchedTime3 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null); + long lastTimerTriggered2 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + // Since token should be good for 2 hrs, lastTokenFetchedTime1 & 2 & 3 all should be the same + // because token is not expired yet. + assertEquals(lastTokenFetchedTime1, lastTokenFetchedTime2); + assertEquals(lastTokenFetchedTime3, lastTokenFetchedTime2); + + // token should be identical since didnt get refreshed + AWSTemporaryCredentials awsCred1b = client.getAWSTemporaryCredentials(domain1, "role1"); + long rt1bExpiry = awsCred1b.getExpiration().millis(); + assertEquals(rt1Expiry, rt1bExpiry); + assertEquals(awsCred1.getSessionToken(), awsCred1b.getSessionToken()); + + // But, make sure the Timer actually triggered. + assertTrue(lastTimerTriggered1 > 0); + assertTrue(lastTimerTriggered2 > 0); + assertNotEquals(lastTimerTriggered1, lastTimerTriggered2); + assertTrue(lastTimerTriggered2 > lastTimerTriggered1); + + client.prefetchAwsCred(domain2, "role2", null, null); + assertEquals(client.getScheduledItemsSize(), scheduledItemsSize + 2); + + AWSTemporaryCredentials awsCred4 = client.getAWSTemporaryCredentials(domain2, "role2"); + assertTrue(awsCred4 != null); + long rtExpiry3 = awsCred4.getExpiration().millis(); + System.out.println("testPrefetchAwsCredShouldNotCallServer: awsCred4:domain=" + domain2 + " role=role2 expires at " + rtExpiry3 + " curtime_millis=" + System.currentTimeMillis()); + + lastTokenFetchedTime3 = ztsClientMock.getLastRoleTokenFetchedTime(domain2, "role2", null); + assertTrue(lastTokenFetchedTime3 > lastTokenFetchedTime2); + + AWSTemporaryCredentials awsCred5 = client.getAWSTemporaryCredentials(domain2, "role1"); + assertTrue(awsCred5 != null); + assertNotEquals(awsCred4.getAccessKeyId(), awsCred5.getAccessKeyId()); + + client.removePrefetcher(); + client.close(); + } + + @Test + public void testPrefetchShouldNotCallServer() throws Exception { + System.out.println("testPrefetchShouldNotCallServer"); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setRoleName("role1"); + long intervalSecs = Integer.parseInt(System.getProperty(ZTSClient.ZTS_CLIENT_PROP_PREFETCH_SLEEP_INTERVAL, "5")); + ztsClientMock.setTestSleepInterval(intervalSecs); + + final Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + SIAClient siaClient = Mockito.mock(SIAClient.class); + Mockito.when(siaClient.getServicePrincipal(Mockito.anyString(), + Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), + Mockito.eq(false))).thenReturn(principal); + + ZTSClient client = new ZTSClient("http://localhost:4080/", "user_domain", "user"); + client.setZTSRDLGeneratedClient(ztsClientMock); + client.setSIAClient(siaClient); + + String domain1 = "coretech"; + String domain2 = "providerdomain"; + ztsClientMock.setAwsCreds(Timestamp.fromCurrentTime(), domain1, "role1"); + ztsClientMock.setAwsCreds(Timestamp.fromCurrentTime(), domain2, "role1"); + + // initially, roleToken was never fetched. + assertTrue(ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null) < 0); + + // initialize the prefetch token process. + client.prefetchRoleToken(domain1, null, null, null, null); + int scheduledItemsSize = client.getScheduledItemsSize(); + + // make sure only unique items are in the queue + client.prefetchRoleToken(domain1, null, null, null, null); + int scheduledItemsSize2 = client.getScheduledItemsSize(); + assertEquals(scheduledItemsSize, scheduledItemsSize2); + + // repeat for aws cred + // + client.prefetchAwsCred(domain1, "role1", null, null); + scheduledItemsSize = client.getScheduledItemsSize(); + assertTrue(scheduledItemsSize > scheduledItemsSize2); + + // make sure only unique items are in the queue + client.prefetchAwsCred(domain1, "role1", null, null); + scheduledItemsSize2 = client.getScheduledItemsSize(); + assertEquals(scheduledItemsSize, scheduledItemsSize2); + + AWSTemporaryCredentials awsCred1 = client.getAWSTemporaryCredentials(domain1, "role1"); + assertTrue(awsCred1 != null); + long awsCredExpiryd1r1 = awsCred1.getExpiration().millis(); + + RoleToken roleToken1 = client.getRoleToken(domain1); + assertTrue(roleToken1 != null); + long rt1Expiry = roleToken1.getExpiryTime(); + + long lastTokenFetchedTime1 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null); + long lastTokenFetchedTime1nr = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null); + + // work with domain2 + // + client.prefetchRoleToken(domain2, null, null, null, null); + scheduledItemsSize2 = client.getScheduledItemsSize(); + assertEquals(scheduledItemsSize2, scheduledItemsSize + 1); + + client.prefetchAwsCred(domain2, "role1", null, null); + scheduledItemsSize2 = client.getScheduledItemsSize(); + assertEquals(scheduledItemsSize2, scheduledItemsSize + 2); + + RoleToken roleToken2 = client.getRoleToken(domain2); + assertTrue(roleToken2 != null); + long rt2Expiry = roleToken2.getExpiryTime(); + + AWSTemporaryCredentials awsCred2 = client.getAWSTemporaryCredentials(domain2, "role1"); + assertTrue(awsCred2 != null); + long awsCredExpiry = awsCred2.getExpiration().millis(); + + System.out.println("testPrefetchShouldNotCallServer: sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchShouldNotCallServer: nap over so what happened"); + + assertEquals(client.getScheduledItemsSize(), scheduledItemsSize + 2); + long lastTimerTriggered1 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + long lastTokenFetchedTimeDom2 = ztsClientMock.getLastRoleTokenFetchedTime(domain2, null, null); + assertTrue(lastTokenFetchedTimeDom2 > 0); + + roleToken2 = client.getRoleToken(domain2); + long rt2Expiry2 = roleToken2.getExpiryTime(); + assertTrue(rt2Expiry2 > rt2Expiry); // this token was refreshed + + awsCred2 = client.getAWSTemporaryCredentials(domain2, "role1"); + long awsCredExpiry2 = awsCred2.getExpiration().millis(); + assertTrue(awsCredExpiry2 > awsCredExpiry); // this cred was refreshed + + // wait a few seconds, and see subsequent fetch happened. + System.out.println("testPrefetchRoleTokenShouldNotCallServer: again sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchShouldNotCallServer: again nap over so what happened"); + + RoleToken roleToken3 = client.getRoleToken(domain2); + long rt2Expiry3 = roleToken3.getExpiryTime(); + System.out.println("testPrefetchShouldNotCallServer: roleToken3:domain=" + domain2 + " expires at " + rt2Expiry3); + assertTrue(rt2Expiry3 > rt2Expiry2); // this token was refreshed + + AWSTemporaryCredentials awsCred3 = client.getAWSTemporaryCredentials(domain2, "role1"); + long awsCredExpiry3 = awsCred3.getExpiration().millis(); + assertTrue(awsCredExpiry3 > awsCredExpiry2); // this cred was refreshed + + long lastTokenFetchedTimed1r1 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null); + long lastTokenFetchedTime3 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null); + + long lastTimerTriggered2 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + // Since token should be good for 2 hrs, lastTokenFetchedTime1 & 2 & 3 all should be the same + // because token is not expired yet. + assertEquals(lastTokenFetchedTime1, lastTokenFetchedTimed1r1); + assertEquals(lastTokenFetchedTime3, lastTokenFetchedTime1nr); + + // token should be identical since didnt get refreshed + RoleToken roleToken1b = client.getRoleToken(domain1); + long rt1bExpiry = roleToken1b.getExpiryTime(); + assertEquals(rt1Expiry, rt1bExpiry); + assertEquals(roleToken1.getToken(), roleToken1b.getToken()); + + // aws cred should be identical since didnt get refreshed + AWSTemporaryCredentials awsCred1b = client.getAWSTemporaryCredentials(domain1, "role1"); + long ac1bExpiry = awsCred1b.getExpiration().millis(); + assertEquals(awsCredExpiryd1r1, ac1bExpiry); + assertEquals(awsCred1.getSessionToken(), awsCred1b.getSessionToken()); + + // But, make sure the Timer actually triggered. + assertTrue(lastTimerTriggered1 > 0); + assertTrue(lastTimerTriggered2 > 0); + assertNotEquals(lastTimerTriggered1, lastTimerTriggered2); + assertTrue(lastTimerTriggered2 > lastTimerTriggered1); + + client.removePrefetcher(); + client.close(); + } + + @Test + public void testPrefetchRoleTokenShouldCallServer() throws Exception { + System.out.println("testPrefetchRoleTokenShouldCallServer"); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + int intervalSecs = Integer.parseInt(System.getProperty(ZTSClient.ZTS_CLIENT_PROP_PREFETCH_SLEEP_INTERVAL, "5")); + ztsClientMock.setTestSleepInterval(intervalSecs); + ztsClientMock.setExpiryTime(intervalSecs); // token expires in 5 seconds + ztsClientMock.setRoleName("role1"); + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + SIAClient siaClient = Mockito.mock(SIAClient.class); + Mockito.when(siaClient.getServicePrincipal(Mockito.anyString(), + Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), + Mockito.eq(false))).thenReturn(principal); + + ZTSClient client = new ZTSClient("http://localhost:4080/", "user_domain", "user"); + client.removePrefetcher(); + client.setZTSRDLGeneratedClient(ztsClientMock); + client.setSIAClient(siaClient); + + String domain1 = "coretech"; + + // initially, roleToken was never fetched. + assertTrue(ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null) < 0); + + // initialize the prefetch token process. + client.prefetchRoleToken(domain1, null, null, null, null); + // make sure only unique items are in the queue + assertEquals(client.getScheduledItemsSize(), 1); + + RoleToken roleToken1 = client.getRoleToken(domain1); + assertTrue(roleToken1 != null); + long rtExpiry = roleToken1.getExpiryTime(); + System.out.println("testPrefetchRoleTokenShouldCallServer: roleToken1:domain=" + domain1 + " expires at " + rtExpiry + " curtime_secs=" + (System.currentTimeMillis() / 1000)); + + System.out.println("testPrefetchRoleTokenShouldCallServer: sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchRoleTokenShouldCallServer: nap over so what happened"); + + assertEquals(client.getScheduledItemsSize(), 1); + + long lastTimerTriggered1 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + long lastTokenFetchedTime1 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null); + roleToken1 = client.getRoleToken(domain1); + long rtExpiry2 = roleToken1.getExpiryTime(); + System.out.println("testPrefetchRoleTokenShouldCallServer: roleToken1:domain=" + domain1 + " expires at " + rtExpiry2 + " curtime_secs=" + (System.currentTimeMillis() / 1000)); + assertTrue(rtExpiry2 > rtExpiry); // this token was refreshed + + assertTrue(lastTokenFetchedTime1 > 0); + + // wait a few seconds, and see subsequent fetch happened. + System.out.println("testPrefetchRoleTokenShouldCallServer: again sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchRoleTokenShouldCallServer: again nap over so what happened"); + + long lastTokenFetchedTime2 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null); + RoleToken roleToken2 = client.getRoleToken(domain1); + long rt2Expiry = roleToken2.getExpiryTime(); + System.out.println("testPrefetchRoleTokenShouldCallServer: roleToken2:domain=" + domain1 + " expires at " + rt2Expiry + " curtime_secs=" + (System.currentTimeMillis() / 1000)); + assertTrue(rt2Expiry > rtExpiry2); // this token was refreshed + + // token should be different + assertNotEquals(roleToken1.getToken(), roleToken2.getToken()); + + long lastTokenFetchedTime3 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null); + + long lastTimerTriggered2 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + // Since token should be good for 5 seconds, lastTokenFetchedTime1 & 2 & 3 all should be different, + assertNotEquals(lastTokenFetchedTime1, lastTokenFetchedTime2); + assertNotEquals(lastTokenFetchedTime3, lastTokenFetchedTime2); + + // make sure the Timer actually triggered. + assertTrue(lastTimerTriggered1 > 0); + assertTrue(lastTimerTriggered2 > 0); + assertNotEquals(lastTimerTriggered1, lastTimerTriggered2); + assertTrue(lastTimerTriggered2 > lastTimerTriggered1); + + client.removePrefetcher(); + client.close(); + } + + @Test + public void testPrefetchAwsCredShouldCallServer() throws Exception { + System.out.println("testPrefetchAwsCredShouldCallServer"); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + int intervalSecs = Integer.parseInt(System.getProperty(ZTSClient.ZTS_CLIENT_PROP_PREFETCH_SLEEP_INTERVAL, "5")); + ztsClientMock.setTestSleepInterval(intervalSecs); + ztsClientMock.setExpiryTime(intervalSecs); // token expires in 5 seconds + ztsClientMock.setRoleName("role1"); + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + SIAClient siaClient = Mockito.mock(SIAClient.class); + Mockito.when(siaClient.getServicePrincipal(Mockito.anyString(), + Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), + Mockito.eq(false))).thenReturn(principal); + + ZTSClient client = new ZTSClient("http://localhost:4080/", "user_domain", "user"); + client.removePrefetcher(); + client.setZTSRDLGeneratedClient(ztsClientMock); + client.setSIAClient(siaClient); + + String domain1 = "coretech"; + ztsClientMock.setAwsCreds(Timestamp.fromCurrentTime(), domain1, "role1"); + ztsClientMock.setAwsCreds(Timestamp.fromCurrentTime(), domain1, "role2"); + + // initially, roleToken was never fetched. + assertTrue(ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null) < 0); + + // initialize the prefetch token process. + client.prefetchAwsCred(domain1, "role1", null, null); + // make sure only unique items are in the queue + long scheduledItemsSize = client.getScheduledItemsSize(); + assertEquals(scheduledItemsSize, 1); + + AWSTemporaryCredentials awsCred1 = client.getAWSTemporaryCredentials(domain1, "role1"); + assertTrue(awsCred1 != null); + long rtExpiry = awsCred1.getExpiration().millis(); + System.out.println("testPrefetchAwsCredShouldCallServer: awsCred1:domain=" + domain1 + " expires at " + rtExpiry + " curtime_millis=" + System.currentTimeMillis()); + + System.out.println("testPrefetchAwsCredShouldCallServer: sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchAwsCredShouldCallServer: nap over so what happened"); + + assertEquals(client.getScheduledItemsSize(), 1); + + long lastTimerTriggered1 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + long lastTokenFetchedTime1 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null); + awsCred1 = client.getAWSTemporaryCredentials(domain1, "role1"); + long rtExpiry2 = awsCred1.getExpiration().millis(); + System.out.println("testPrefetchAwsCredShouldCallServer: roleToken1:domain=" + domain1 + " expires at " + rtExpiry2 + " curtime_millis=" + System.currentTimeMillis()); + assertTrue(rtExpiry2 > rtExpiry); // this token was refreshed + + assertTrue(lastTokenFetchedTime1 > 0); + + // wait a few seconds, and see subsequent fetch happened. + System.out.println("testPrefetchAwsCredShouldCallServer: again sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchAwsCredShouldCallServer: again nap over so what happened"); + + long lastTokenFetchedTime2 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null); + AWSTemporaryCredentials awsCred2 = client.getAWSTemporaryCredentials(domain1, "role1"); + long rt2Expiry = awsCred2.getExpiration().millis(); + System.out.println("testPrefetchAwsCredShouldCallServer: awsCred2:domain=" + domain1 + " expires at " + rt2Expiry + " curtime_millis=" + System.currentTimeMillis()); + assertTrue(rt2Expiry > rtExpiry2); // this token was refreshed + + // token should be different + assertNotEquals(awsCred1.getSessionToken(), awsCred2.getSessionToken()); + + long lastTokenFetchedTime3 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null); + + long lastTimerTriggered2 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + // Since token should be good for 5 seconds, lastTokenFetchedTime1 & 2 & 3 all should be different, + assertNotEquals(lastTokenFetchedTime1, lastTokenFetchedTime2); + assertNotEquals(lastTokenFetchedTime3, lastTokenFetchedTime2); + + // make sure the Timer actually triggered. + assertTrue(lastTimerTriggered1 > 0); + assertTrue(lastTimerTriggered2 > 0); + assertNotEquals(lastTimerTriggered1, lastTimerTriggered2); + assertTrue(lastTimerTriggered2 > lastTimerTriggered1); + + client.prefetchAwsCred(domain1, "role2", null, null); + assertEquals(client.getScheduledItemsSize(), scheduledItemsSize + 1); + + AWSTemporaryCredentials awsCred4 = client.getAWSTemporaryCredentials(domain1, "role2"); + assertTrue(awsCred4 != null); + long rtExpiry3 = awsCred4.getExpiration().millis(); + System.out.println("testPrefetchAwsCredShouldCallServer: awsCred4:domain=" + domain1 + " role=role2 expires at " + rtExpiry3 + " curtime_millis=" + System.currentTimeMillis()); + + lastTokenFetchedTime3 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role2", null); + assertTrue(lastTokenFetchedTime3 > lastTokenFetchedTime2); + + AWSTemporaryCredentials awsCred5 = client.getAWSTemporaryCredentials(domain1, "role1"); + assertTrue(awsCred5 != null); + assertNotEquals(awsCred4.getAccessKeyId(), awsCred5.getAccessKeyId()); + + client.removePrefetcher(); + client.close(); + } + + @Test + public void testPrefetchShouldCallServer() throws Exception { + System.out.println("testPrefetchShouldCallServer"); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + int intervalSecs = Integer.parseInt(System.getProperty(ZTSClient.ZTS_CLIENT_PROP_PREFETCH_SLEEP_INTERVAL, "5")); + ztsClientMock.setTestSleepInterval(intervalSecs); + ztsClientMock.setExpiryTime(intervalSecs); // token expires in 5 seconds + ztsClientMock.setRoleName("role1"); + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + SIAClient siaClient = Mockito.mock(SIAClient.class); + Mockito.when(siaClient.getServicePrincipal(Mockito.anyString(), + Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), + Mockito.eq(false))).thenReturn(principal); + + ZTSClient client = new ZTSClient("http://localhost:4080/", "user_domain", "user"); + client.removePrefetcher(); + client.setZTSRDLGeneratedClient(ztsClientMock); + client.setSIAClient(siaClient); + + String domain1 = "coretech"; + ztsClientMock.setAwsCreds(Timestamp.fromCurrentTime(), domain1, "role1"); + + // initially, roleToken was never fetched. + assertTrue(ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null) < 0); + assertTrue(ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null) < 0); + + // initialize the prefetch token process. + client.prefetchRoleToken(domain1, null, null, null, null); + // make sure only unique items are in the queue + assertEquals(client.getScheduledItemsSize(), 1); + + // initialize the prefetch aws processing + client.prefetchAwsCred(domain1, "role1", null, null); + assertEquals(client.getScheduledItemsSize(), 2); + + RoleToken roleToken1 = client.getRoleToken(domain1); + assertTrue(roleToken1 != null); + long rtExpiry = roleToken1.getExpiryTime(); + System.out.println("testPrefetchShouldCallServer: roleToken1:domain=" + domain1 + " expires at " + rtExpiry + " curtime_secs=" + (System.currentTimeMillis() / 1000)); + + AWSTemporaryCredentials awsCred1 = client.getAWSTemporaryCredentials(domain1, "role1"); + assertTrue(awsCred1 != null); + long awsExpiry = awsCred1.getExpiration().millis(); + System.out.println("testPrefetchShouldCallServer: awsCred1:domain=" + domain1 + " expires at " + awsExpiry + " curtime_millis=" + System.currentTimeMillis()); + assertEquals(client.getScheduledItemsSize(), 2); + + System.out.println("testPrefetchShouldCallServer: sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchShouldCallServer: nap over so what happened"); + + assertEquals(client.getScheduledItemsSize(), 2); + + long lastTimerTriggered1 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + long lastTokenFetchedTime1 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null); + roleToken1 = client.getRoleToken(domain1); + long rtExpiry2 = roleToken1.getExpiryTime(); + System.out.println("testPrefetchShouldCallServer: roleToken1:domain=" + domain1 + " expires at " + rtExpiry2 + " curtime_secs=" + (System.currentTimeMillis() / 1000)); + assertTrue(rtExpiry2 > rtExpiry); // this token was refreshed + + assertTrue(lastTokenFetchedTime1 > 0); + + long lastTokenFetchedTime1aws = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null); + AWSTemporaryCredentials awsCredLast = client.getAWSTemporaryCredentials(domain1, "role1"); + long awsExpiry2 = awsCredLast.getExpiration().millis(); + System.out.println("testPrefetchShouldCallServer: awsCred1:domain=" + domain1 + " expires at " + awsExpiry2 + " curtime_millis=" + System.currentTimeMillis()); + assertTrue(awsExpiry2 > awsExpiry); // this token was refreshed + + assertTrue(lastTokenFetchedTime1aws > 0); + + // wait a few seconds, and see subsequent fetch happened. + System.out.println("testPrefetchShouldCallServer: again sleep Secs=" + (2*intervalSecs) + "+0.1"); + Thread.sleep((2 * intervalSecs * 1000) + 100); + System.out.println("testPrefetchShouldCallServer: again nap over so what happened"); + + long lastTokenFetchedTime2 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null); + long lastTokenFetchedTime2aws = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null); + + RoleToken roleToken2 = client.getRoleToken(domain1); + long rt2Expiry = roleToken2.getExpiryTime(); + System.out.println("testPrefetchShouldCallServer: roleToken2:domain=" + domain1 + " expires at " + rt2Expiry + " curtime_secs=" + (System.currentTimeMillis() / 1000)); + assertTrue(rt2Expiry > rtExpiry2); // this token was refreshed + + // token should be different + assertNotEquals(roleToken1.getToken(), roleToken2.getToken()); + + long lastTokenFetchedTime3 = ztsClientMock.getLastRoleTokenFetchedTime(domain1, null, null); + + AWSTemporaryCredentials awsCred3 = client.getAWSTemporaryCredentials(domain1, "role1"); + long awsExpiry3 = awsCred3.getExpiration().millis(); + System.out.println("testPrefetchShouldCallServer: awsCred3:domain=" + domain1 + " expires at " + awsExpiry3 + " curtime_millis=" + System.currentTimeMillis()); + assertTrue(awsExpiry3 > awsExpiry2); // this token was refreshed + + // token should be different + assertNotEquals(awsCredLast.getSessionToken(), awsCred3.getSessionToken()); + + long lastTokenFetchedTime3aws = ztsClientMock.getLastRoleTokenFetchedTime(domain1, "role1", null); + + long lastTimerTriggered2 = ZTSClient.FETCHER_LAST_RUN_AT.get(); + + // Since token should be good for 5 seconds, lastTokenFetchedTime1 & 2 & 3 all should be different, + assertNotEquals(lastTokenFetchedTime1, lastTokenFetchedTime2); + assertNotEquals(lastTokenFetchedTime3, lastTokenFetchedTime2); + + assertNotEquals(lastTokenFetchedTime1aws, lastTokenFetchedTime2aws); + assertNotEquals(lastTokenFetchedTime3aws, lastTokenFetchedTime2aws); + + // make sure the Timer actually triggered. + assertTrue(lastTimerTriggered1 > 0); + assertTrue(lastTimerTriggered2 > 0); + assertNotEquals(lastTimerTriggered1, lastTimerTriggered2); + assertTrue(lastTimerTriggered2 > lastTimerTriggered1); + + client.removePrefetcher(); + client.close(); + } + + @Test + public void testGetHostServices() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.removePrefetcher(); + client.setZTSRDLGeneratedClient(ztsClientMock); + + HostServices hostServices = client.getHostServices("host.exist"); + + assertEquals(hostServices.getHost(), "host.exist"); + assertEquals(hostServices.getNames().size(), 2); + + try { + client.getHostServices("not.exist.host"); + fail(); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 404); + } + + client.close(); + } + + @Test + public void testGetPublicKeyEntry() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.removePrefetcher(); + client.setZTSRDLGeneratedClient(ztsClientMock); + + PublicKeyEntry publicKeyEntry = client.getPublicKeyEntry("coretech", "storage", "key1"); + assertEquals(publicKeyEntry.getId(), "key1"); + assertEquals(publicKeyEntry.getKey(), "test-key"); + + try { + client.getPublicKeyEntry("invalid.domain", "storage", "key1"); + fail(); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 404); + } + + client.close(); + } + + @Test + public void testGetServiceIdentity() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.removePrefetcher(); + client.setZTSRDLGeneratedClient(ztsClientMock); + + ServiceIdentity serviceIdentity = client.getServiceIdentity("coretech", "storage"); + assertEquals(serviceIdentity.getName(), "storage"); + + try { + client.getServiceIdentity("unknown.domain", "storage"); + fail(); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 404); + } + + client.close(); + } + + @Test + public void testGetServiceIdentityList() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.removePrefetcher(); + client.setZTSRDLGeneratedClient(ztsClientMock); + + ServiceIdentityList serviceIdentityList = client.getServiceIdentityList("coretech"); + assertEquals(serviceIdentityList.getNames(), Arrays.asList("storage")); + + try { + client.getServiceIdentityList("unknown.domain"); + fail(); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 404); + } + + client.close(); + } + + @Test + public void testGetRoleTokenName() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setRoleName("role1"); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.removePrefetcher(); + client.setZTSRDLGeneratedClient(ztsClientMock); + + RoleToken roleToken = client.getRoleToken("coretech", "role1"); + assertNotNull(roleToken); + + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getDomain(), "coretech"); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("role1")); + + try { + roleToken = client.getRoleToken("coretech", "role2"); + fail(); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 403); + } + client.close(); + } + + @Test + public void testGetRoleTokenSuffixInvalid() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setRoleName("role1"); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.removePrefetcher(); + client.setZTSRDLGeneratedClient(ztsClientMock); + + try { + client.getRoleToken("coretech", null); + fail(); + } catch (IllegalArgumentException ex) { + // expected exception + assertTrue(true); + } catch (Exception ex) { + fail(); + } + + try { + client.getRoleToken("coretech", ""); + fail(); + } catch (IllegalArgumentException ex) { + // expected exception + assertTrue(true); + } catch (Exception ex) { + fail(); + } + client.close(); + } + + @Test + public void testGetRoleTokenCacheExpire() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setRoleName("role1"); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.removePrefetcher(); + client.setZTSRDLGeneratedClient(ztsClientMock); + + RoleToken roleToken = client.getRoleToken("coretech"); + assertNotNull(roleToken); + + // now we're going to get a token again without any expiry timeouts and this time + // we should get back from our cache thus the same exact one + + RoleToken roleToken2 = client.getRoleToken("coretech"); + assertTrue(roleToken2.getToken().equals(roleToken.getToken())); + + // now we're going to use the full API to request the token with timeouts + // that should satisfy the expiry time and thus get back the same one + + roleToken2 = client.getRoleToken("coretech", null, 1800, 3600, false); + assertTrue(roleToken2.getToken().equals(roleToken.getToken())); + + // this time we're going to ask for an increased min expiry time + // thus the cache should no longer be satisfied + + roleToken2 = client.getRoleToken("coretech", null, 2800, 3600, false); + assertFalse(roleToken2.getToken().equals(roleToken.getToken())); + client.close(); + } + + @Test + public void testGetDomainSignedPolicyDataNull() { + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + + Map> responseHeaders = new HashMap<>(); + DomainSignedPolicyData domainSignedPolicyData = client.getDomainSignedPolicyData("coretech", null, responseHeaders); + assertNull(domainSignedPolicyData); + client.close(); + } + + @Test + public void testGetDomainSignedPolicyData() { + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setPolicyName("policy1"); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + + Map> responseHeaders = new HashMap<>(); + DomainSignedPolicyData domainSignedPolicyData = client.getDomainSignedPolicyData("coretech", null, responseHeaders); + assertNotNull(domainSignedPolicyData); + + SignedPolicyData signedPolicyData = domainSignedPolicyData.getSignedPolicyData(); + assertNotNull(signedPolicyData); + assertEquals(signedPolicyData.getZmsSignature(), "zmsSignature"); + assertEquals(signedPolicyData.getZmsKeyId(), "0"); + + try { + client.getDomainSignedPolicyData(null, null, responseHeaders); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + PolicyData policyData = signedPolicyData.getPolicyData(); + assertNotNull(policyData); + assertEquals(policyData.getDomain(), "coretech"); + + List policyList = policyData.getPolicies(); + assertNotNull(policyList); + assertEquals(policyList.size(), 1); + assertEquals(policyList.get(0).getName(), "policy1"); + client.close(); + } + + @Test + public void testGetTenantDomains() { + ZTSClientMock ztsClientMock = new ZTSClientMock(); + List tenantDomains = new ArrayList<>(); + tenantDomains.add("iaas.athenz"); + tenantDomains.add("coretech.storage"); + ztsClientMock.setTenantDomains(tenantDomains); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + + TenantDomains doms = client.getTenantDomains("provider", "user", "admin", "storage"); + assertNotNull(doms); + assertTrue(doms.getTenantDomainNames().contains("iaas.athenz")); + assertTrue(doms.getTenantDomainNames().contains("coretech.storage")); + + try { + client.getTenantDomains("unknown", "user", "admin", "storage"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } catch (Exception ex) { + fail(); + } + + client.close(); + } + + @Test + public void testGetAWSTemporaryCredentials() { + + Timestamp currentTime = Timestamp.fromCurrentTime(); + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setAwsCreds(currentTime, "coretech", "role", "sessionToken", "secretAccessKey", "accessKeyId"); + + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + + AWSTemporaryCredentials awsCreds = client.getAWSTemporaryCredentials("coretech", "role"); + assertNotNull(awsCreds); + assertEquals("accessKeyId", awsCreds.getAccessKeyId()); + assertEquals("secretAccessKey", awsCreds.getSecretAccessKey()); + assertTrue(awsCreds.getSessionToken().startsWith("sessionToken")); + currentTime = awsCreds.getExpiration(); + + AWSTemporaryCredentials awsCreds2 = client.getAWSTemporaryCredentials("coretech", "role"); + assertNotNull(awsCreds2); + assertEquals("accessKeyId", awsCreds2.getAccessKeyId()); + assertEquals("secretAccessKey", awsCreds2.getSecretAccessKey()); + assertTrue(awsCreds2.getSessionToken().startsWith("sessionToken")); + assertEquals(currentTime.millis() / 1000, awsCreds2.getExpiration().millis() / 1000); + + // now let's try with invalid domain/role values; + + assertNull(client.getAWSTemporaryCredentials("coretech", "role1")); + assertNull(client.getAWSTemporaryCredentials("coretech1", "role")); + + client.close(); + } + + @Test + public void testGetAWSTemporaryCredentialsException() { + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + + try { + client.getAWSTemporaryCredentials("coretech", "role"); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 404); + } + + client.close(); + } + + @Test + public void testHostnameVerifierSupport() { + + ZTSRDLGeneratedClientMock client = new ZTSRDLGeneratedClientMock("http://localhost:4080", null); + assertNull(client.getHostnameVerifier()); + + HostnameVerifier hostnameVerifier = new ZTSClientTest.TestHostVerifier(); + client = new ZTSRDLGeneratedClientMock("http://localhost:4080", null, hostnameVerifier); + assertNotNull(client.getHostnameVerifier()); + } + + @Test + public void testHostnamVerifierDnsMatchStandard() { + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + ZTSClient.AWSHostNameVerifier hostnameVerifier = client.new AWSHostNameVerifier("host1"); + + ArrayList> altNames = new ArrayList<>(); + ArrayList rfcName = new ArrayList<>(); + rfcName.add(Integer.valueOf(1)); + rfcName.add("rfcname"); + altNames.add(rfcName); + + ArrayList dnsName = new ArrayList<>(); + dnsName.add(Integer.valueOf(2)); + dnsName.add("host1"); + altNames.add(dnsName); + + assertTrue(hostnameVerifier.matchDnsHostname(altNames)); + + ArrayList> altNames2 = new ArrayList<>(); + ArrayList rfcName2 = new ArrayList<>(); + rfcName2.add(Integer.valueOf(1)); + rfcName2.add("rfcname"); + altNames2.add(rfcName2); + + ArrayList dnsName2 = new ArrayList<>(); + dnsName2.add(Integer.valueOf(2)); + dnsName2.add("host11"); + altNames2.add(dnsName2); + + assertFalse(hostnameVerifier.matchDnsHostname(altNames2)); + + client.close(); + } + + @Test + public void testHostnamVerifierDnsMatchWildcard() { + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + ZTSClient.AWSHostNameVerifier hostnameVerifier = client.new AWSHostNameVerifier("*.host1"); + + ArrayList> altNames = new ArrayList<>(); + ArrayList rfcName = new ArrayList<>(); + rfcName.add(Integer.valueOf(1)); + rfcName.add("rfcname"); + altNames.add(rfcName); + + ArrayList dnsName = new ArrayList<>(); + dnsName.add(Integer.valueOf(2)); + dnsName.add("*.host1"); + altNames.add(dnsName); + + assertTrue(hostnameVerifier.matchDnsHostname(altNames)); + + ArrayList> altNames2 = new ArrayList<>(); + ArrayList rfcName2 = new ArrayList<>(); + rfcName2.add(Integer.valueOf(1)); + rfcName2.add("rfcname"); + altNames2.add(rfcName2); + + ArrayList dnsName2 = new ArrayList<>(); + dnsName2.add(Integer.valueOf(2)); + dnsName2.add("*.host11"); + altNames2.add(dnsName2); + + assertFalse(hostnameVerifier.matchDnsHostname(altNames2)); + + client.close(); + } + + @Test + public void testHostnamVerifierDnsMatchNone() { + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + ZTSClient.AWSHostNameVerifier hostnameVerifier = client.new AWSHostNameVerifier("host1"); + + ArrayList> altNames = new ArrayList<>(); + ArrayList rfcName = new ArrayList<>(); + rfcName.add(Integer.valueOf(1)); + rfcName.add("rfcname"); + altNames.add(rfcName); + + ArrayList dnsName = new ArrayList<>(); + dnsName.add(Integer.valueOf(3)); + dnsName.add("host1"); + altNames.add(dnsName); + + assertFalse(hostnameVerifier.matchDnsHostname(altNames)); + client.close(); + } + + @Test + public void testHostnamVerifierDnsNull() { + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + ZTSClient.AWSHostNameVerifier hostnameVerifier = client.new AWSHostNameVerifier("host1"); + + assertFalse(hostnameVerifier.matchDnsHostname(null)); + client.close(); + } + + private static class TestHostVerifier implements HostnameVerifier { + + public boolean verify(String hostname, SSLSession session) { + return true; + } + } + + @Test + public void testPostInstanceRefreshRequest() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.setZTSRDLGeneratedClient(ztsClientMock); + + InstanceRefreshRequest req = new InstanceRefreshRequest().setExpiryTime(600); + Identity identity = client.postInstanceRefreshRequest("coretech", "unit", req); + assertNotNull(identity); + assertNotNull(identity.getServiceToken()); + client.close(); + } + + @Test + public void testPostInstanceRefreshRequestException() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.setZTSRDLGeneratedClient(ztsClientMock); + + InstanceRefreshRequest req = new InstanceRefreshRequest().setExpiryTime(600); + try { + client.postInstanceRefreshRequest("exc", "unit", req); + fail(); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 400); + } + client.close(); + } + + @Test + public void testGetRoleTokenWithUserData() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ztsClientMock.setRoleName("role1"); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.setZTSRDLGeneratedClient(ztsClientMock); + + RoleToken roleToken = client.getRoleToken("coretech", null, null, null, true, "user_domain.user4"); + assertNotNull(roleToken); + + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getDomain(), "coretech"); + assertEquals(1, token.getRoles().size()); + assertTrue(token.getRoles().contains("role1")); + assertEquals(token.getProxyUser(), "user_domain.user4"); + + client.close(); + } + + @Test + public void testGetRoleAccess() { + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + + RoleAccess roleAccess = client.getRoleAccess("coretech", "yby.user"); + List roles = roleAccess.getRoles(); + assertEquals(roles.size(), 2); + assertTrue(roles.contains("role1")); + + try { + client.getRoleAccess("exc", "yby.user"); + fail(); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 400); + } + + try { + client.getRoleAccess("unknown", "yby.user"); + fail(); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 404); + } + client.close(); + } + + @Test + public void testGetAccess() { + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + + Access access = client.getAccess("coretech", "match", "user_domain.user1"); + assertTrue(access.getGranted()); + + access = client.getAccess("coretech", "no-match", "user_domain.user1"); + assertFalse(access.getGranted()); + + try { + client.getAccess("exc", "match", "user_domain.user1"); + fail(); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 400); + } + + client.close(); + } + + @Test + public void testPostDomainMetrics() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.setZTSRDLGeneratedClient(ztsClientMock); + + List metricList = new ArrayList<>(); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED_DENY_NO_MATCH). + setMetricVal(99)); + DomainMetrics req = new DomainMetrics(). + setDomainName("coretech"). + setMetricList(metricList); + client.postDomainMetrics("coretech", req); + client.close(); + } + + @Test + public void testPostDomainMetricsBadRequest() { + + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.setZTSRDLGeneratedClient(ztsClientMock); + + List metricList = new ArrayList<>(); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED_DENY_NO_MATCH). + setMetricVal(99)); + DomainMetrics req = new DomainMetrics(). + setDomainName("coretech"). + setMetricList(metricList); + try { + client.postDomainMetrics("exc", req); + fail(); + } catch (ZTSClientException ex) { + assertEquals(ex.getCode(), 400); + } + client.close(); + } + + @Test + public void testPrefetchInterval() { + Principal principal = SimplePrincipal.create("user_domain", "user", "auth_creds", PRINCIPAL_AUTHORITY); + ZTSClient client = new ZTSClient("http://localhost:4080", principal); + client.setPrefetchInterval(10L); + assertEquals(client.getPrefetchInterval(), 10l); + + client.close(); + } + + @Test + public void testException() { + + List codes = Arrays.asList(200, 201, 202, 204, 301, 302, 303, 304 + ,307, 400, 401, 403, 404, 409, 410, 412, 415, 500, 501, 503); + + for (int code : codes) { + ResourceException e = new ResourceException(code); + assertNotNull(e.getData()); + } + + ResourceException ex = new ResourceException(400); + + assertEquals(ex.getCode(), 400); + assertEquals(ex.getData().toString(), "{code: 400, message: \"Bad Request\"}"); + } + + @Test + public void testHostNameVerifierVerifyCertNull() throws SSLPeerUnverifiedException { + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + ZTSClient.AWSHostNameVerifier hostnameVerifier = client.new AWSHostNameVerifier("host1"); + + SSLSession session = Mockito.mock(SSLSession.class); + Mockito.when(session.getPeerCertificates()).thenReturn(null); + + assertFalse(hostnameVerifier.verify("host1", session)); + + System.out.println("hashCode:" + client.hashCode()); + + client.close(); + } + + + final static String crlf = System.getProperty("line.separator"); + final static String test_cert = + "-----BEGIN CERTIFICATE-----"+crlf + + "MIIDRDCCAiwCCQDltWO9Xjhd8DANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJK"+crlf + + "UDEOMAwGA1UECBMFVG9reW8xEjAQBgNVBAcTCVN1bWlkYS1rdTEPMA0GA1UEChMG"+crlf + + "WUpURVNUMQ8wDQYDVQQLEwZZSlRFU1QxDzANBgNVBAMTBllKVEVTVDAeFw0xNjEx"+crlf + + "MTEwODMzMTVaFw0yNjExMDkwODMzMTVaMGQxCzAJBgNVBAYTAkpQMQ4wDAYDVQQI"+crlf + + "EwVUb2t5bzESMBAGA1UEBxMJU3VtaWRhLWt1MQ8wDQYDVQQKEwZZSlRFU1QxDzAN"+crlf + + "BgNVBAsTBllKVEVTVDEPMA0GA1UEAxMGWUpURVNUMIIBIjANBgkqhkiG9w0BAQEF"+crlf + + "AAOCAQ8AMIIBCgKCAQEA1Ssz+hLCTXyMlDH9E0bd9EEm0yNyPH4XhtUkSEDdYE+Z"+crlf + + "0m/7BkfrKTRRew8wrfpLkK0wZsoVkEjwd0GktZXnGTRUs42Bd5tSYXV1Z78oqjS4"+crlf + + "AGpjkQlQva+f6ANnDhPNxHJ6QlY6DLePIByjepmJS8UZGRuNPiDpbtWWhuCLbn6p"+crlf + + "to2SiclLHr6K/5uFYjawS8k3bGmoV9QfeWvY+aiGvuxDsPCxcePpwSA8btubpTsJ"+crlf + + "CvC31rJChgN5VQFE26vfhVCwmuhwOCcUThdgaI9LAjLETknrLt/kiFaiIhm5peSG"+crlf + + "t0DP89u9fnaUX7P8jc/4V57lnp+ynRpGpHfv4Fi4wQIDAQABMA0GCSqGSIb3DQEB"+crlf + + "BQUAA4IBAQARH92fKPsVoCq80ARt70LM8ynaq9dlXcLjr34CWINbGbXG4a0RP1l9"+crlf + + "bFZih7rCG96W+fDKxvgR2YwXhRJq5NchOoBB0mtOBG3VwbXFNm6CBHqwtbrNiPzv"+crlf + + "BvK7jerZd1g0CgTWzfoPgO/87F2uX5J92CsvXRYrDJsFYHnhmUg3JWCT4q+Xe9J4"+crlf + + "/Eyw+C1DgDwWjjBB1Qb3QBO/dpGR+EWv4mtNK8D2o+iEFJLjtNdqIkcrUIXfqI8M"+crlf + + "z+7Tph5eLFgI5lEW+Pu/myzLIXCNWoRr7UQute898v/1XZiRS4sSCEQSgXnZflA8"+crlf + + "c2KrYjMGSUogzw6+1gKeucygV32rA2B2"+crlf + + "-----END CERTIFICATE-----"; + + @Test + public void testHostNameVerifierVerifyCert() throws CertificateException, IOException { + ZTSClientMock ztsClientMock = new ZTSClientMock(); + ZTSClient client = new ZTSClient("http://localhost:4080", (Principal) null); + client.setZTSRDLGeneratedClient(ztsClientMock); + ZTSClient.AWSHostNameVerifier hostnameVerifier = client.new AWSHostNameVerifier("host1"); + + InputStream is = new ByteArrayInputStream(test_cert.getBytes("utf-8")); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + java.security.cert.Certificate cert = cf.generateCertificate(is); + is.close(); + + Certificate[] certs = new Certificate[1]; certs[0] = cert; + + SSLSession session = Mockito.mock(SSLSession.class); + Mockito.when(session.getPeerCertificates()).thenReturn(certs); + + + assertFalse(hostnameVerifier.verify("unknown", session)); + client.close(); + } +} diff --git a/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClientMock.java b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClientMock.java new file mode 100644 index 00000000000..fc47d4fef67 --- /dev/null +++ b/clients/java/zts/src/test/java/com/yahoo/athenz/zts/ZTSRDLGeneratedClientMock.java @@ -0,0 +1,45 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import javax.net.ssl.HostnameVerifier; + +import com.yahoo.athenz.auth.Principal; + +public class ZTSRDLGeneratedClientMock extends ZTSRDLGeneratedClient { + + public ZTSRDLGeneratedClientMock(String url) { + super(url); + } + + public ZTSRDLGeneratedClientMock(String url, Principal identity) { + super(url); + if (identity != null && identity.getAuthority() != null) { + addCredentials(identity.getAuthority().getHeader(), identity.getCredentials()); + } + } + + public ZTSRDLGeneratedClientMock(String url, Principal identity, HostnameVerifier hostnameVerifier) { + super(url, hostnameVerifier); + if (identity != null && identity.getAuthority() != null) { + addCredentials(identity.getAuthority().getHeader(), identity.getCredentials()); + } + } + + public HostnameVerifier getHostnameVerifier() { + return client.getHostnameVerifier(); + } +} diff --git a/clients/java/zts/src/test/resources/META-INF/services/com.yahoo.athenz.zts.ZTSClientService b/clients/java/zts/src/test/resources/META-INF/services/com.yahoo.athenz.zts.ZTSClientService new file mode 100644 index 00000000000..2ad3df47c5c --- /dev/null +++ b/clients/java/zts/src/test/resources/META-INF/services/com.yahoo.athenz.zts.ZTSClientService @@ -0,0 +1 @@ +com.yahoo.athenz.zts.ZTSClientServiceProvider diff --git a/clients/java/zts/src/test/resources/athenz.conf b/clients/java/zts/src/test/resources/athenz.conf new file mode 100644 index 00000000000..bddf2f268aa --- /dev/null +++ b/clients/java/zts/src/test/resources/athenz.conf @@ -0,0 +1,17 @@ +{ + "zmsUrl": "https://dev.zms.athenzcompany.com:4443/", + "ztsUrl": "https://dev.zts.athenzcompany.com:4443/", + "ztsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + } + ], + "zmsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + } + ] +} + diff --git a/core/zms/README.md b/core/zms/README.md new file mode 100644 index 00000000000..d9a36f84d6e --- /dev/null +++ b/core/zms/README.md @@ -0,0 +1,12 @@ +ZMS Core Support +================ + +Contains the ZMS interface and support classes. + +The interface, schema, and support classes are generated from the ZMS RDL definition herein. Clients and servers will use these classes to implement the interface. + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/core/zms/pom.xml b/core/zms/pom.xml new file mode 100644 index 00000000000..c382b4b4995 --- /dev/null +++ b/core/zms/pom.xml @@ -0,0 +1,67 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../pom.xml + + + zms_core + jar + zms_core + Core ZMS Interfaces + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.1.1 + + + + exec + + generate-sources + + + + bash + + scripts/make_stubs.sh + + + + + + + diff --git a/core/zms/scripts/make_stubs.sh b/core/zms/scripts/make_stubs.sh new file mode 100755 index 00000000000..4fcbae50071 --- /dev/null +++ b/core/zms/scripts/make_stubs.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# If any of the RDL files have been updated, then this script should be run +# rdl to generate the appropriate model classes. + +# Note this script is dependent on the rdl utility. +# go get github.com/ardielle/ardielle-tools/... +# + +command -v rdl >/dev/null 2>&1 || { + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "SOURCE WARNING"; + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "Please install rdl utility: go get github.com/ardielle/ardielle-tools/..."; + echo >&2 "Skipping source generation..."; + exit 0; +} + +RDL_ZMS_FILE=src/main/rdl/ZMS.rdl +RDL_PROVIDER_FILE=src/main/rdl/Provider.rdl + +echo "Generating model classes..." +rdl -s generate -x getsetters=true -o src/main/java java-model $RDL_ZMS_FILE +rdl -s generate -x getsetters=true -o src/main/java java-model $RDL_PROVIDER_FILE + +# Copyright 2016 Yahoo Inc. +# Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. diff --git a/core/zms/src/main/java/com/yahoo/athenz/provider/ProviderSchema.java b/core/zms/src/main/java/com/yahoo/athenz/provider/ProviderSchema.java new file mode 100644 index 00000000000..6de2a5e53d2 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/provider/ProviderSchema.java @@ -0,0 +1,187 @@ +// +// This file generated by rdl 1.4.8 +// + +package com.yahoo.athenz.provider; +import com.yahoo.rdl.*; + +public class ProviderSchema { + + private final static Schema INSTANCE = build(); + public static Schema instance() { + return INSTANCE; + } + + private static Schema build() { + SchemaBuilder sb = new SchemaBuilder("Provider"); + sb.version(1); + sb.namespace("com.yahoo.athenz.provider"); + sb.comment("Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. This \"service\" definition is just to provide a generic client stub for any service implementing the provider API"); + + sb.stringType("SimpleName") + .comment("Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. Common name types used by several API definitions A simple identifier, an element of compound name.") + .pattern("[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("CompoundName") + .comment("A compound name. Most names in this API are compound names.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("DomainName") + .comment("A domain name is the general qualifier prefix, as its uniqueness is managed.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("EntityName") + .comment("An entity name is a short form of a resource name, including only the domain and entity.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("ServiceName") + .comment("A service name will generally be a unique subdomain.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("LocationName") + .comment("A location name is not yet defined, but will be a dotted name like everything else.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("ActionName") + .comment("An action (operation) name.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("ResourceName") + .comment("A shorthand for a YRN with no service or location. The 'tail' of a YRN, just the domain:entity. Note that the EntityName part is optional, that is, a domain name followed by a colon is valid resource name.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*(:([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?"); + + sb.stringType("YRN") + .comment("A full Yahoo Resource name (YRN).") + .pattern("(yrn:(([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?:(([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?:)?([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*(:([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?"); + + sb.stringType("YBase64") + .comment("The Y-specific URL-safe Base64 variant.") + .pattern("[a-zA-Z0-9\\._-]+"); + + sb.stringType("YEncoded") + .comment("YEncoded includes ybase64 chars, as well as = and %. This can represent a user cookie and URL-encoded values.") + .pattern("[a-zA-Z0-9\\._%=-]*"); + + sb.stringType("AuthorityName") + .comment("Used as the prefix in a signed assertion. This uniquely identifies a signing authority. i.e. \"user\"") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("SignedToken") + .comment("A signed assertion if identity. i.e. the user cookie value. This token will only make sense to the authority that generated it, so it is beneficial to have something in the value that is cheaply recognized to quickly reject if it belongs to another authority. In addition to the YEncoded set our token includes ; to separate components and , to separate roles and : for IPv6 addresses") + .pattern("[a-zA-Z0-9\\._%=:;,-]*"); + + sb.enumType("TenantState") + .element("INACTIVE") + .element("PENDING") + .element("ACTIVE"); + + sb.structType("Tenant") + .field("service", "SimpleName", false, "name of the service") + .field("name", "DomainName", false, "name of the tenant domain in this service. Must be a valid domain name the caller has rights to") + .field("state", "TenantState", false, "the state of the tenant", null) + .arrayField("roles", "EntityName", true, "the roles this tenant may assume. Determined by and returned by this service") + .arrayField("resourceGroups", "EntityName", true, "registered resource groups for this tenant"); + + sb.structType("TenantResourceGroup") + .field("service", "SimpleName", false, "name of the service") + .field("name", "DomainName", false, "name of the tenant domain in this service. Must be a valid domain name the caller has rights to") + .field("resourceGroup", "EntityName", false, "resource group for this tenant") + .arrayField("roles", "EntityName", true, "the roles this tenant may assume. Determined by and returned by this service"); + + + sb.resource("Tenant", "PUT", "/service/{service}/tenant/{tenant}") + .comment("Create a new tenant in this provider note optional 3rd arg in this case -- it is unusual!") + .pathParam("service", "SimpleName", "name of the service") + .pathParam("tenant", "DomainName", "name of the tenant domain in this service") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("template", "Tenant", "") + .auth("assume_role", "role.{service}.tenant.{tenant}.admin", false, "{tenant}") + .expected("OK") + .exception("ACCEPTED", "Tenant", "") + + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Tenant", "GET", "/service/{service}/tenant/{tenant}") + .comment("Get information about the tenant") + .pathParam("service", "SimpleName", "name of the service") + .pathParam("tenant", "DomainName", "name of the tenant domain in this service") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Tenant", "DELETE", "/service/{service}/tenant/{tenant}") + .comment("Remove a tenant and all its resources. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("service", "SimpleName", "name of the service") + .pathParam("tenant", "DomainName", "name of the tenant domain in this service") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("assume_role", "role.{service}.tenant.{tenant}.admin", false, "{tenant}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TenantResourceGroup", "PUT", "/service/{service}/tenant/{tenant}/resourceGroup/{resourceGroup}") + .comment("Create a new resource group for this tenant in this provider. The tenant must already be registered with the provider") + .pathParam("service", "SimpleName", "name of the service") + .pathParam("tenant", "DomainName", "name of the tenant domain in this service") + .pathParam("resourceGroup", "EntityName", "tenant resource group") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("template", "TenantResourceGroup", "") + .auth("assume_role", "role.{service}.tenant.{tenant}.admin", false, "{tenant}") + .expected("OK") + .exception("ACCEPTED", "Tenant", "") + + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TenantResourceGroup", "DELETE", "/service/{service}/tenant/{tenant}/resourceGroup/{resourceGroup}") + .comment("Remove the specified resource group from this tenant and all its resources.") + .pathParam("service", "SimpleName", "name of the service") + .pathParam("tenant", "DomainName", "name of the tenant domain in this service") + .pathParam("resourceGroup", "EntityName", "tenant resource group") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("assume_role", "role.{service}.tenant.{tenant}.admin", false, "{tenant}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + + return sb.build(); + } + +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/provider/Tenant.java b/core/zms/src/main/java/com/yahoo/athenz/provider/Tenant.java new file mode 100644 index 00000000000..749739e7303 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/provider/Tenant.java @@ -0,0 +1,85 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.provider; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Tenant - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Tenant { + public String service; + public String name; + @RdlOptional + public TenantState state; + @RdlOptional + public List roles; + @RdlOptional + public List resourceGroups; + + public Tenant setService(String service) { + this.service = service; + return this; + } + public String getService() { + return service; + } + public Tenant setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public Tenant setState(TenantState state) { + this.state = state; + return this; + } + public TenantState getState() { + return state; + } + public Tenant setRoles(List roles) { + this.roles = roles; + return this; + } + public List getRoles() { + return roles; + } + public Tenant setResourceGroups(List resourceGroups) { + this.resourceGroups = resourceGroups; + return this; + } + public List getResourceGroups() { + return resourceGroups; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Tenant.class) { + return false; + } + Tenant a = (Tenant) another; + if (service == null ? a.service != null : !service.equals(a.service)) { + return false; + } + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (state == null ? a.state != null : !state.equals(a.state)) { + return false; + } + if (roles == null ? a.roles != null : !roles.equals(a.roles)) { + return false; + } + if (resourceGroups == null ? a.resourceGroups != null : !resourceGroups.equals(a.resourceGroups)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/provider/TenantResourceGroup.java b/core/zms/src/main/java/com/yahoo/athenz/provider/TenantResourceGroup.java new file mode 100644 index 00000000000..126c0ae1227 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/provider/TenantResourceGroup.java @@ -0,0 +1,72 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.provider; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// TenantResourceGroup - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class TenantResourceGroup { + public String service; + public String name; + public String resourceGroup; + @RdlOptional + public List roles; + + public TenantResourceGroup setService(String service) { + this.service = service; + return this; + } + public String getService() { + return service; + } + public TenantResourceGroup setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public TenantResourceGroup setResourceGroup(String resourceGroup) { + this.resourceGroup = resourceGroup; + return this; + } + public String getResourceGroup() { + return resourceGroup; + } + public TenantResourceGroup setRoles(List roles) { + this.roles = roles; + return this; + } + public List getRoles() { + return roles; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != TenantResourceGroup.class) { + return false; + } + TenantResourceGroup a = (TenantResourceGroup) another; + if (service == null ? a.service != null : !service.equals(a.service)) { + return false; + } + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (resourceGroup == null ? a.resourceGroup != null : !resourceGroup.equals(a.resourceGroup)) { + return false; + } + if (roles == null ? a.roles != null : !roles.equals(a.roles)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/provider/TenantState.java b/core/zms/src/main/java/com/yahoo/athenz/provider/TenantState.java new file mode 100644 index 00000000000..42e6a8e1a42 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/provider/TenantState.java @@ -0,0 +1,24 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.provider; +import com.yahoo.rdl.*; + +// +// TenantState - +// +public enum TenantState { + INACTIVE, + PENDING, + ACTIVE; + + public static TenantState fromString(String v) { + for (TenantState e : values()) { + if (e.toString().equals(v)) { + return e; + } + } + throw new IllegalArgumentException("Invalid string representation for TenantState: " + v); + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/Access.java b/core/zms/src/main/java/com/yahoo/athenz/zms/Access.java new file mode 100644 index 00000000000..198b737ee50 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/Access.java @@ -0,0 +1,37 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Access - Access can be checked and returned as this resource. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Access { + public boolean granted; + + public Access setGranted(boolean granted) { + this.granted = granted; + return this; + } + public boolean getGranted() { + return granted; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Access.class) { + return false; + } + Access a = (Access) another; + if (granted != a.granted) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/Assertion.java b/core/zms/src/main/java/com/yahoo/athenz/zms/Assertion.java new file mode 100644 index 00000000000..db563490a24 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/Assertion.java @@ -0,0 +1,84 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Assertion - A representation for the encapsulation of an action to be +// performed on a resource by a principal. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Assertion { + public String role; + public String resource; + public String action; + @RdlOptional + public AssertionEffect effect; + @RdlOptional + public Long id; + + public Assertion setRole(String role) { + this.role = role; + return this; + } + public String getRole() { + return role; + } + public Assertion setResource(String resource) { + this.resource = resource; + return this; + } + public String getResource() { + return resource; + } + public Assertion setAction(String action) { + this.action = action; + return this; + } + public String getAction() { + return action; + } + public Assertion setEffect(AssertionEffect effect) { + this.effect = effect; + return this; + } + public AssertionEffect getEffect() { + return effect; + } + public Assertion setId(Long id) { + this.id = id; + return this; + } + public Long getId() { + return id; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Assertion.class) { + return false; + } + Assertion a = (Assertion) another; + if (role == null ? a.role != null : !role.equals(a.role)) { + return false; + } + if (resource == null ? a.resource != null : !resource.equals(a.resource)) { + return false; + } + if (action == null ? a.action != null : !action.equals(a.action)) { + return false; + } + if (effect == null ? a.effect != null : !effect.equals(a.effect)) { + return false; + } + if (id == null ? a.id != null : !id.equals(a.id)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/AssertionEffect.java b/core/zms/src/main/java/com/yahoo/athenz/zms/AssertionEffect.java new file mode 100644 index 00000000000..76042be9fa2 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/AssertionEffect.java @@ -0,0 +1,23 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; + +// +// AssertionEffect - Every assertion can have the effect of ALLOW or DENY. +// +public enum AssertionEffect { + ALLOW, + DENY; + + public static AssertionEffect fromString(String v) { + for (AssertionEffect e : values()) { + if (e.toString().equals(v)) { + return e; + } + } + throw new IllegalArgumentException("Invalid string representation for AssertionEffect: " + v); + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/DanglingPolicy.java b/core/zms/src/main/java/com/yahoo/athenz/zms/DanglingPolicy.java new file mode 100644 index 00000000000..77d7c65db94 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/DanglingPolicy.java @@ -0,0 +1,49 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DanglingPolicy - A dangling policy where the assertion is referencing a role +// name that doesn't exist in the domain +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DanglingPolicy { + public String policyName; + public String roleName; + + public DanglingPolicy setPolicyName(String policyName) { + this.policyName = policyName; + return this; + } + public String getPolicyName() { + return policyName; + } + public DanglingPolicy setRoleName(String roleName) { + this.roleName = roleName; + return this; + } + public String getRoleName() { + return roleName; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DanglingPolicy.class) { + return false; + } + DanglingPolicy a = (DanglingPolicy) another; + if (policyName == null ? a.policyName != null : !policyName.equals(a.policyName)) { + return false; + } + if (roleName == null ? a.roleName != null : !roleName.equals(a.roleName)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/DefaultAdmins.java b/core/zms/src/main/java/com/yahoo/athenz/zms/DefaultAdmins.java new file mode 100644 index 00000000000..17f337cf4f9 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/DefaultAdmins.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DefaultAdmins - The list of domain administrators. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DefaultAdmins { + public List admins; + + public DefaultAdmins setAdmins(List admins) { + this.admins = admins; + return this; + } + public List getAdmins() { + return admins; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DefaultAdmins.class) { + return false; + } + DefaultAdmins a = (DefaultAdmins) another; + if (admins == null ? a.admins != null : !admins.equals(a.admins)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/Domain.java b/core/zms/src/main/java/com/yahoo/athenz/zms/Domain.java new file mode 100644 index 00000000000..d17b8dec05c --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/Domain.java @@ -0,0 +1,151 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Domain - A domain is an independent partition of users, roles, and +// resources. Its name represents the definition of a namespace; the only way a +// new namespace can be created, from the top, is by creating Domains. +// Administration of a domain is governed by the parent domain (using +// reverse-DNS namespaces). The top level domains are governed by the special +// "sys.auth" domain. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Domain { + public String name; + @RdlOptional + public Timestamp modified; + @RdlOptional + public UUID id; + @RdlOptional + public String description; + @RdlOptional + public String org; + @RdlOptional + public Boolean enabled; + @RdlOptional + public Boolean auditEnabled; + @RdlOptional + public String account; + @RdlOptional + public Integer ypmId; + + public Domain setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public Domain setModified(Timestamp modified) { + this.modified = modified; + return this; + } + public Timestamp getModified() { + return modified; + } + public Domain setId(UUID id) { + this.id = id; + return this; + } + public UUID getId() { + return id; + } + public Domain setDescription(String description) { + this.description = description; + return this; + } + public String getDescription() { + return description; + } + public Domain setOrg(String org) { + this.org = org; + return this; + } + public String getOrg() { + return org; + } + public Domain setEnabled(Boolean enabled) { + this.enabled = enabled; + return this; + } + public Boolean getEnabled() { + return enabled; + } + public Domain setAuditEnabled(Boolean auditEnabled) { + this.auditEnabled = auditEnabled; + return this; + } + public Boolean getAuditEnabled() { + return auditEnabled; + } + public Domain setAccount(String account) { + this.account = account; + return this; + } + public String getAccount() { + return account; + } + public Domain setYpmId(Integer ypmId) { + this.ypmId = ypmId; + return this; + } + public Integer getYpmId() { + return ypmId; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Domain.class) { + return false; + } + Domain a = (Domain) another; + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (modified == null ? a.modified != null : !modified.equals(a.modified)) { + return false; + } + if (id == null ? a.id != null : !id.equals(a.id)) { + return false; + } + if (description == null ? a.description != null : !description.equals(a.description)) { + return false; + } + if (org == null ? a.org != null : !org.equals(a.org)) { + return false; + } + if (enabled == null ? a.enabled != null : !enabled.equals(a.enabled)) { + return false; + } + if (auditEnabled == null ? a.auditEnabled != null : !auditEnabled.equals(a.auditEnabled)) { + return false; + } + if (account == null ? a.account != null : !account.equals(a.account)) { + return false; + } + if (ypmId == null ? a.ypmId != null : !ypmId.equals(a.ypmId)) { + return false; + } + } + return true; + } + + // + // sets up the instance according to its default field values, if any + // + public Domain init() { + if (enabled == null) { + enabled = true; + } + if (auditEnabled == null) { + auditEnabled = false; + } + return this; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/DomainData.java b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainData.java new file mode 100644 index 00000000000..70a74608d1c --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainData.java @@ -0,0 +1,117 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainData - A domain object that includes its roles, policies and services. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainData { + public String name; + @RdlOptional + public String account; + @RdlOptional + public Integer ypmId; + public List roles; + public SignedPolicies policies; + public List services; + public List entities; + public Timestamp modified; + + public DomainData setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public DomainData setAccount(String account) { + this.account = account; + return this; + } + public String getAccount() { + return account; + } + public DomainData setYpmId(Integer ypmId) { + this.ypmId = ypmId; + return this; + } + public Integer getYpmId() { + return ypmId; + } + public DomainData setRoles(List roles) { + this.roles = roles; + return this; + } + public List getRoles() { + return roles; + } + public DomainData setPolicies(SignedPolicies policies) { + this.policies = policies; + return this; + } + public SignedPolicies getPolicies() { + return policies; + } + public DomainData setServices(List services) { + this.services = services; + return this; + } + public List getServices() { + return services; + } + public DomainData setEntities(List entities) { + this.entities = entities; + return this; + } + public List getEntities() { + return entities; + } + public DomainData setModified(Timestamp modified) { + this.modified = modified; + return this; + } + public Timestamp getModified() { + return modified; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainData.class) { + return false; + } + DomainData a = (DomainData) another; + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (account == null ? a.account != null : !account.equals(a.account)) { + return false; + } + if (ypmId == null ? a.ypmId != null : !ypmId.equals(a.ypmId)) { + return false; + } + if (roles == null ? a.roles != null : !roles.equals(a.roles)) { + return false; + } + if (policies == null ? a.policies != null : !policies.equals(a.policies)) { + return false; + } + if (services == null ? a.services != null : !services.equals(a.services)) { + return false; + } + if (entities == null ? a.entities != null : !entities.equals(a.entities)) { + return false; + } + if (modified == null ? a.modified != null : !modified.equals(a.modified)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/DomainDataCheck.java b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainDataCheck.java new file mode 100644 index 00000000000..c45a6f992a6 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainDataCheck.java @@ -0,0 +1,110 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainDataCheck - Domain data object representing the results of a check +// operation looking for dangling roles, policies and trust relationships that +// are set either on tenant or provider side only +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainDataCheck { + @RdlOptional + public List danglingRoles; + @RdlOptional + public List danglingPolicies; + public int policyCount; + public int assertionCount; + public int roleWildCardCount; + @RdlOptional + public List providersWithoutTrust; + @RdlOptional + public List tenantsWithoutAssumeRole; + + public DomainDataCheck setDanglingRoles(List danglingRoles) { + this.danglingRoles = danglingRoles; + return this; + } + public List getDanglingRoles() { + return danglingRoles; + } + public DomainDataCheck setDanglingPolicies(List danglingPolicies) { + this.danglingPolicies = danglingPolicies; + return this; + } + public List getDanglingPolicies() { + return danglingPolicies; + } + public DomainDataCheck setPolicyCount(int policyCount) { + this.policyCount = policyCount; + return this; + } + public int getPolicyCount() { + return policyCount; + } + public DomainDataCheck setAssertionCount(int assertionCount) { + this.assertionCount = assertionCount; + return this; + } + public int getAssertionCount() { + return assertionCount; + } + public DomainDataCheck setRoleWildCardCount(int roleWildCardCount) { + this.roleWildCardCount = roleWildCardCount; + return this; + } + public int getRoleWildCardCount() { + return roleWildCardCount; + } + public DomainDataCheck setProvidersWithoutTrust(List providersWithoutTrust) { + this.providersWithoutTrust = providersWithoutTrust; + return this; + } + public List getProvidersWithoutTrust() { + return providersWithoutTrust; + } + public DomainDataCheck setTenantsWithoutAssumeRole(List tenantsWithoutAssumeRole) { + this.tenantsWithoutAssumeRole = tenantsWithoutAssumeRole; + return this; + } + public List getTenantsWithoutAssumeRole() { + return tenantsWithoutAssumeRole; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainDataCheck.class) { + return false; + } + DomainDataCheck a = (DomainDataCheck) another; + if (danglingRoles == null ? a.danglingRoles != null : !danglingRoles.equals(a.danglingRoles)) { + return false; + } + if (danglingPolicies == null ? a.danglingPolicies != null : !danglingPolicies.equals(a.danglingPolicies)) { + return false; + } + if (policyCount != a.policyCount) { + return false; + } + if (assertionCount != a.assertionCount) { + return false; + } + if (roleWildCardCount != a.roleWildCardCount) { + return false; + } + if (providersWithoutTrust == null ? a.providersWithoutTrust != null : !providersWithoutTrust.equals(a.providersWithoutTrust)) { + return false; + } + if (tenantsWithoutAssumeRole == null ? a.tenantsWithoutAssumeRole != null : !tenantsWithoutAssumeRole.equals(a.tenantsWithoutAssumeRole)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/DomainList.java b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainList.java new file mode 100644 index 00000000000..5f989056306 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainList.java @@ -0,0 +1,50 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainList - A paginated list of domains. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainList { + public List names; + @RdlOptional + public String next; + + public DomainList setNames(List names) { + this.names = names; + return this; + } + public List getNames() { + return names; + } + public DomainList setNext(String next) { + this.next = next; + return this; + } + public String getNext() { + return next; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainList.class) { + return false; + } + DomainList a = (DomainList) another; + if (names == null ? a.names != null : !names.equals(a.names)) { + return false; + } + if (next == null ? a.next != null : !next.equals(a.next)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/DomainMeta.java b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainMeta.java new file mode 100644 index 00000000000..e15d64feebe --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainMeta.java @@ -0,0 +1,112 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainMeta - Set of metadata attributes that all domains may have and can be +// changed. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainMeta { + @RdlOptional + public String description; + @RdlOptional + public String org; + @RdlOptional + public Boolean enabled; + @RdlOptional + public Boolean auditEnabled; + @RdlOptional + public String account; + @RdlOptional + public Integer ypmId; + + public DomainMeta setDescription(String description) { + this.description = description; + return this; + } + public String getDescription() { + return description; + } + public DomainMeta setOrg(String org) { + this.org = org; + return this; + } + public String getOrg() { + return org; + } + public DomainMeta setEnabled(Boolean enabled) { + this.enabled = enabled; + return this; + } + public Boolean getEnabled() { + return enabled; + } + public DomainMeta setAuditEnabled(Boolean auditEnabled) { + this.auditEnabled = auditEnabled; + return this; + } + public Boolean getAuditEnabled() { + return auditEnabled; + } + public DomainMeta setAccount(String account) { + this.account = account; + return this; + } + public String getAccount() { + return account; + } + public DomainMeta setYpmId(Integer ypmId) { + this.ypmId = ypmId; + return this; + } + public Integer getYpmId() { + return ypmId; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainMeta.class) { + return false; + } + DomainMeta a = (DomainMeta) another; + if (description == null ? a.description != null : !description.equals(a.description)) { + return false; + } + if (org == null ? a.org != null : !org.equals(a.org)) { + return false; + } + if (enabled == null ? a.enabled != null : !enabled.equals(a.enabled)) { + return false; + } + if (auditEnabled == null ? a.auditEnabled != null : !auditEnabled.equals(a.auditEnabled)) { + return false; + } + if (account == null ? a.account != null : !account.equals(a.account)) { + return false; + } + if (ypmId == null ? a.ypmId != null : !ypmId.equals(a.ypmId)) { + return false; + } + } + return true; + } + + // + // sets up the instance according to its default field values, if any + // + public DomainMeta init() { + if (enabled == null) { + enabled = true; + } + if (auditEnabled == null) { + auditEnabled = false; + } + return this; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/DomainModified.java b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainModified.java new file mode 100644 index 00000000000..84ad813706a --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainModified.java @@ -0,0 +1,50 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainModified - Tuple of domain-name and modification time-stamps. This +// object is returned when the caller has requested list of domains modified +// since a specific timestamp. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainModified { + public String name; + public long modified; + + public DomainModified setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public DomainModified setModified(long modified) { + this.modified = modified; + return this; + } + public long getModified() { + return modified; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainModified.class) { + return false; + } + DomainModified a = (DomainModified) another; + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (modified != a.modified) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/DomainModifiedList.java b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainModifiedList.java new file mode 100644 index 00000000000..d89a21a7f07 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainModifiedList.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainModifiedList - A list of {domain, modified-timestamp} tuples. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainModifiedList { + public List nameModList; + + public DomainModifiedList setNameModList(List nameModList) { + this.nameModList = nameModList; + return this; + } + public List getNameModList() { + return nameModList; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainModifiedList.class) { + return false; + } + DomainModifiedList a = (DomainModifiedList) another; + if (nameModList == null ? a.nameModList != null : !nameModList.equals(a.nameModList)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/DomainPolicies.java b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainPolicies.java new file mode 100644 index 00000000000..4fb3059257a --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainPolicies.java @@ -0,0 +1,52 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainPolicies - We need to include the name of the domain in this struct +// since this data will be passed back to ZPU through ZTS so we need to sign not +// only the list of policies but also the corresponding domain name that the +// policies belong to. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainPolicies { + public String domain; + public List policies; + + public DomainPolicies setDomain(String domain) { + this.domain = domain; + return this; + } + public String getDomain() { + return domain; + } + public DomainPolicies setPolicies(List policies) { + this.policies = policies; + return this; + } + public List getPolicies() { + return policies; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainPolicies.class) { + return false; + } + DomainPolicies a = (DomainPolicies) another; + if (domain == null ? a.domain != null : !domain.equals(a.domain)) { + return false; + } + if (policies == null ? a.policies != null : !policies.equals(a.policies)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/DomainTemplate.java b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainTemplate.java new file mode 100644 index 00000000000..ec08e6ba27d --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainTemplate.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainTemplate - solution template(s) to be applied to a domain +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainTemplate { + public List templateNames; + + public DomainTemplate setTemplateNames(List templateNames) { + this.templateNames = templateNames; + return this; + } + public List getTemplateNames() { + return templateNames; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainTemplate.class) { + return false; + } + DomainTemplate a = (DomainTemplate) another; + if (templateNames == null ? a.templateNames != null : !templateNames.equals(a.templateNames)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/DomainTemplateList.java b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainTemplateList.java new file mode 100644 index 00000000000..1829e32f07e --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/DomainTemplateList.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainTemplateList - List of solution templates to be applied to a domain +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainTemplateList { + public List templateNames; + + public DomainTemplateList setTemplateNames(List templateNames) { + this.templateNames = templateNames; + return this; + } + public List getTemplateNames() { + return templateNames; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainTemplateList.class) { + return false; + } + DomainTemplateList a = (DomainTemplateList) another; + if (templateNames == null ? a.templateNames != null : !templateNames.equals(a.templateNames)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/Entity.java b/core/zms/src/main/java/com/yahoo/athenz/zms/Entity.java new file mode 100644 index 00000000000..a111ac7513d --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/Entity.java @@ -0,0 +1,50 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Entity - An entity is a name and a structured value. some entity +// names/prefixes are reserved (i.e. "role", "policy", "meta", "domain", +// "service") +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Entity { + public String name; + public Struct value; + + public Entity setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public Entity setValue(Struct value) { + this.value = value; + return this; + } + public Struct getValue() { + return value; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Entity.class) { + return false; + } + Entity a = (Entity) another; + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (value == null ? a.value != null : !value.equals(a.value)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/EntityList.java b/core/zms/src/main/java/com/yahoo/athenz/zms/EntityList.java new file mode 100644 index 00000000000..c706902bf24 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/EntityList.java @@ -0,0 +1,39 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// EntityList - The representation for an enumeration of entities in the +// namespace +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class EntityList { + public List names; + + public EntityList setNames(List names) { + this.names = names; + return this; + } + public List getNames() { + return names; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != EntityList.class) { + return false; + } + EntityList a = (EntityList) another; + if (names == null ? a.names != null : !names.equals(a.names)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/HostServices.java b/core/zms/src/main/java/com/yahoo/athenz/zms/HostServices.java new file mode 100644 index 00000000000..f08a9b6e460 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/HostServices.java @@ -0,0 +1,50 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// HostServices - The representation for an enumeration of services authorized +// to run on a specific host. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class HostServices { + public String host; + public List names; + + public HostServices setHost(String host) { + this.host = host; + return this; + } + public String getHost() { + return host; + } + public HostServices setNames(List names) { + this.names = names; + return this; + } + public List getNames() { + return names; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != HostServices.class) { + return false; + } + HostServices a = (HostServices) another; + if (host == null ? a.host != null : !host.equals(a.host)) { + return false; + } + if (names == null ? a.names != null : !names.equals(a.names)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/Membership.java b/core/zms/src/main/java/com/yahoo/athenz/zms/Membership.java new file mode 100644 index 00000000000..a8046eef971 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/Membership.java @@ -0,0 +1,71 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Membership - The representation for a role membership. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Membership { + public String memberName; + @RdlOptional + public Boolean isMember; + @RdlOptional + public String roleName; + + public Membership setMemberName(String memberName) { + this.memberName = memberName; + return this; + } + public String getMemberName() { + return memberName; + } + public Membership setIsMember(Boolean isMember) { + this.isMember = isMember; + return this; + } + public Boolean getIsMember() { + return isMember; + } + public Membership setRoleName(String roleName) { + this.roleName = roleName; + return this; + } + public String getRoleName() { + return roleName; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Membership.class) { + return false; + } + Membership a = (Membership) another; + if (memberName == null ? a.memberName != null : !memberName.equals(a.memberName)) { + return false; + } + if (isMember == null ? a.isMember != null : !isMember.equals(a.isMember)) { + return false; + } + if (roleName == null ? a.roleName != null : !roleName.equals(a.roleName)) { + return false; + } + } + return true; + } + + // + // sets up the instance according to its default field values, if any + // + public Membership init() { + if (isMember == null) { + isMember = true; + } + return this; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/Policies.java b/core/zms/src/main/java/com/yahoo/athenz/zms/Policies.java new file mode 100644 index 00000000000..1e8fa310c8f --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/Policies.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Policies - The representation of list of policy objects +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Policies { + public List list; + + public Policies setList(List list) { + this.list = list; + return this; + } + public List getList() { + return list; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Policies.class) { + return false; + } + Policies a = (Policies) another; + if (list == null ? a.list != null : !list.equals(a.list)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/Policy.java b/core/zms/src/main/java/com/yahoo/athenz/zms/Policy.java new file mode 100644 index 00000000000..6607047f521 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/Policy.java @@ -0,0 +1,61 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Policy - The representation for a Policy with set of assertions. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Policy { + public String name; + @RdlOptional + public Timestamp modified; + public List assertions; + + public Policy setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public Policy setModified(Timestamp modified) { + this.modified = modified; + return this; + } + public Timestamp getModified() { + return modified; + } + public Policy setAssertions(List assertions) { + this.assertions = assertions; + return this; + } + public List getAssertions() { + return assertions; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Policy.class) { + return false; + } + Policy a = (Policy) another; + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (modified == null ? a.modified != null : !modified.equals(a.modified)) { + return false; + } + if (assertions == null ? a.assertions != null : !assertions.equals(a.assertions)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/PolicyList.java b/core/zms/src/main/java/com/yahoo/athenz/zms/PolicyList.java new file mode 100644 index 00000000000..73ffe089701 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/PolicyList.java @@ -0,0 +1,51 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// PolicyList - The representation for an enumeration of policies in the +// namespace, with pagination. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class PolicyList { + public List names; + @RdlOptional + public String next; + + public PolicyList setNames(List names) { + this.names = names; + return this; + } + public List getNames() { + return names; + } + public PolicyList setNext(String next) { + this.next = next; + return this; + } + public String getNext() { + return next; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != PolicyList.class) { + return false; + } + PolicyList a = (PolicyList) another; + if (names == null ? a.names != null : !names.equals(a.names)) { + return false; + } + if (next == null ? a.next != null : !next.equals(a.next)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/ProviderResourceGroupRoles.java b/core/zms/src/main/java/com/yahoo/athenz/zms/ProviderResourceGroupRoles.java new file mode 100644 index 00000000000..42ebd1c287e --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/ProviderResourceGroupRoles.java @@ -0,0 +1,83 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// ProviderResourceGroupRoles - A representation of provider roles to be +// provisioned. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class ProviderResourceGroupRoles { + public String domain; + public String service; + public String tenant; + public List roles; + public String resourceGroup; + + public ProviderResourceGroupRoles setDomain(String domain) { + this.domain = domain; + return this; + } + public String getDomain() { + return domain; + } + public ProviderResourceGroupRoles setService(String service) { + this.service = service; + return this; + } + public String getService() { + return service; + } + public ProviderResourceGroupRoles setTenant(String tenant) { + this.tenant = tenant; + return this; + } + public String getTenant() { + return tenant; + } + public ProviderResourceGroupRoles setRoles(List roles) { + this.roles = roles; + return this; + } + public List getRoles() { + return roles; + } + public ProviderResourceGroupRoles setResourceGroup(String resourceGroup) { + this.resourceGroup = resourceGroup; + return this; + } + public String getResourceGroup() { + return resourceGroup; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != ProviderResourceGroupRoles.class) { + return false; + } + ProviderResourceGroupRoles a = (ProviderResourceGroupRoles) another; + if (domain == null ? a.domain != null : !domain.equals(a.domain)) { + return false; + } + if (service == null ? a.service != null : !service.equals(a.service)) { + return false; + } + if (tenant == null ? a.tenant != null : !tenant.equals(a.tenant)) { + return false; + } + if (roles == null ? a.roles != null : !roles.equals(a.roles)) { + return false; + } + if (resourceGroup == null ? a.resourceGroup != null : !resourceGroup.equals(a.resourceGroup)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/PublicKeyEntry.java b/core/zms/src/main/java/com/yahoo/athenz/zms/PublicKeyEntry.java new file mode 100644 index 00000000000..50c55eb3d10 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/PublicKeyEntry.java @@ -0,0 +1,49 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// PublicKeyEntry - The representation of the public key in a service identity +// object. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class PublicKeyEntry { + public String key; + public String id; + + public PublicKeyEntry setKey(String key) { + this.key = key; + return this; + } + public String getKey() { + return key; + } + public PublicKeyEntry setId(String id) { + this.id = id; + return this; + } + public String getId() { + return id; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != PublicKeyEntry.class) { + return false; + } + PublicKeyEntry a = (PublicKeyEntry) another; + if (key == null ? a.key != null : !key.equals(a.key)) { + return false; + } + if (id == null ? a.id != null : !id.equals(a.id)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/ResourceAccess.java b/core/zms/src/main/java/com/yahoo/athenz/zms/ResourceAccess.java new file mode 100644 index 00000000000..eb715130423 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/ResourceAccess.java @@ -0,0 +1,49 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// ResourceAccess - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class ResourceAccess { + public String principal; + public List assertions; + + public ResourceAccess setPrincipal(String principal) { + this.principal = principal; + return this; + } + public String getPrincipal() { + return principal; + } + public ResourceAccess setAssertions(List assertions) { + this.assertions = assertions; + return this; + } + public List getAssertions() { + return assertions; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != ResourceAccess.class) { + return false; + } + ResourceAccess a = (ResourceAccess) another; + if (principal == null ? a.principal != null : !principal.equals(a.principal)) { + return false; + } + if (assertions == null ? a.assertions != null : !assertions.equals(a.assertions)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/ResourceAccessList.java b/core/zms/src/main/java/com/yahoo/athenz/zms/ResourceAccessList.java new file mode 100644 index 00000000000..c92c8527876 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/ResourceAccessList.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// ResourceAccessList - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class ResourceAccessList { + public List resources; + + public ResourceAccessList setResources(List resources) { + this.resources = resources; + return this; + } + public List getResources() { + return resources; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != ResourceAccessList.class) { + return false; + } + ResourceAccessList a = (ResourceAccessList) another; + if (resources == null ? a.resources != null : !resources.equals(a.resources)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/Role.java b/core/zms/src/main/java/com/yahoo/athenz/zms/Role.java new file mode 100644 index 00000000000..b25a95d1a8f --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/Role.java @@ -0,0 +1,86 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Role - The representation for a Role with set of members. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Role { + public String name; + @RdlOptional + public Timestamp modified; + @RdlOptional + public List members; + @RdlOptional + public String trust; + @RdlOptional + public List auditLog; + + public Role setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public Role setModified(Timestamp modified) { + this.modified = modified; + return this; + } + public Timestamp getModified() { + return modified; + } + public Role setMembers(List members) { + this.members = members; + return this; + } + public List getMembers() { + return members; + } + public Role setTrust(String trust) { + this.trust = trust; + return this; + } + public String getTrust() { + return trust; + } + public Role setAuditLog(List auditLog) { + this.auditLog = auditLog; + return this; + } + public List getAuditLog() { + return auditLog; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Role.class) { + return false; + } + Role a = (Role) another; + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (modified == null ? a.modified != null : !modified.equals(a.modified)) { + return false; + } + if (members == null ? a.members != null : !members.equals(a.members)) { + return false; + } + if (trust == null ? a.trust != null : !trust.equals(a.trust)) { + return false; + } + if (auditLog == null ? a.auditLog != null : !auditLog.equals(a.auditLog)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/RoleAuditLog.java b/core/zms/src/main/java/com/yahoo/athenz/zms/RoleAuditLog.java new file mode 100644 index 00000000000..2f63366458d --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/RoleAuditLog.java @@ -0,0 +1,82 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// RoleAuditLog - An audit log entry for role membership change. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class RoleAuditLog { + public String member; + public String admin; + public Timestamp created; + public String action; + @RdlOptional + public String auditRef; + + public RoleAuditLog setMember(String member) { + this.member = member; + return this; + } + public String getMember() { + return member; + } + public RoleAuditLog setAdmin(String admin) { + this.admin = admin; + return this; + } + public String getAdmin() { + return admin; + } + public RoleAuditLog setCreated(Timestamp created) { + this.created = created; + return this; + } + public Timestamp getCreated() { + return created; + } + public RoleAuditLog setAction(String action) { + this.action = action; + return this; + } + public String getAction() { + return action; + } + public RoleAuditLog setAuditRef(String auditRef) { + this.auditRef = auditRef; + return this; + } + public String getAuditRef() { + return auditRef; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != RoleAuditLog.class) { + return false; + } + RoleAuditLog a = (RoleAuditLog) another; + if (member == null ? a.member != null : !member.equals(a.member)) { + return false; + } + if (admin == null ? a.admin != null : !admin.equals(a.admin)) { + return false; + } + if (created == null ? a.created != null : !created.equals(a.created)) { + return false; + } + if (action == null ? a.action != null : !action.equals(a.action)) { + return false; + } + if (auditRef == null ? a.auditRef != null : !auditRef.equals(a.auditRef)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/RoleList.java b/core/zms/src/main/java/com/yahoo/athenz/zms/RoleList.java new file mode 100644 index 00000000000..111bf74f8e9 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/RoleList.java @@ -0,0 +1,51 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// RoleList - The representation for an enumeration of roles in the namespace, +// with pagination. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class RoleList { + public List names; + @RdlOptional + public String next; + + public RoleList setNames(List names) { + this.names = names; + return this; + } + public List getNames() { + return names; + } + public RoleList setNext(String next) { + this.next = next; + return this; + } + public String getNext() { + return next; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != RoleList.class) { + return false; + } + RoleList a = (RoleList) another; + if (names == null ? a.names != null : !names.equals(a.names)) { + return false; + } + if (next == null ? a.next != null : !next.equals(a.next)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/Roles.java b/core/zms/src/main/java/com/yahoo/athenz/zms/Roles.java new file mode 100644 index 00000000000..305421fc193 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/Roles.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Roles - The representation for a list of roles with full details +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Roles { + public List list; + + public Roles setList(List list) { + this.list = list; + return this; + } + public List getList() { + return list; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Roles.class) { + return false; + } + Roles a = (Roles) another; + if (list == null ? a.list != null : !list.equals(a.list)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/ServerTemplateList.java b/core/zms/src/main/java/com/yahoo/athenz/zms/ServerTemplateList.java new file mode 100644 index 00000000000..489775190d9 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/ServerTemplateList.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// ServerTemplateList - List of solution templates available in the server +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class ServerTemplateList { + public List templateNames; + + public ServerTemplateList setTemplateNames(List templateNames) { + this.templateNames = templateNames; + return this; + } + public List getTemplateNames() { + return templateNames; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != ServerTemplateList.class) { + return false; + } + ServerTemplateList a = (ServerTemplateList) another; + if (templateNames == null ? a.templateNames != null : !templateNames.equals(a.templateNames)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentities.java b/core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentities.java new file mode 100644 index 00000000000..832ed6b28be --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentities.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// ServiceIdentities - The representation of list of services +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class ServiceIdentities { + public List list; + + public ServiceIdentities setList(List list) { + this.list = list; + return this; + } + public List getList() { + return list; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != ServiceIdentities.class) { + return false; + } + ServiceIdentities a = (ServiceIdentities) another; + if (list == null ? a.list != null : !list.equals(a.list)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentity.java b/core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentity.java new file mode 100644 index 00000000000..e68a9b7a762 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentity.java @@ -0,0 +1,122 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// ServiceIdentity - The representation of the service identity object. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class ServiceIdentity { + public String name; + @RdlOptional + public List publicKeys; + @RdlOptional + public String providerEndpoint; + @RdlOptional + public Timestamp modified; + @RdlOptional + public String executable; + @RdlOptional + public List hosts; + @RdlOptional + public String user; + @RdlOptional + public String group; + + public ServiceIdentity setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public ServiceIdentity setPublicKeys(List publicKeys) { + this.publicKeys = publicKeys; + return this; + } + public List getPublicKeys() { + return publicKeys; + } + public ServiceIdentity setProviderEndpoint(String providerEndpoint) { + this.providerEndpoint = providerEndpoint; + return this; + } + public String getProviderEndpoint() { + return providerEndpoint; + } + public ServiceIdentity setModified(Timestamp modified) { + this.modified = modified; + return this; + } + public Timestamp getModified() { + return modified; + } + public ServiceIdentity setExecutable(String executable) { + this.executable = executable; + return this; + } + public String getExecutable() { + return executable; + } + public ServiceIdentity setHosts(List hosts) { + this.hosts = hosts; + return this; + } + public List getHosts() { + return hosts; + } + public ServiceIdentity setUser(String user) { + this.user = user; + return this; + } + public String getUser() { + return user; + } + public ServiceIdentity setGroup(String group) { + this.group = group; + return this; + } + public String getGroup() { + return group; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != ServiceIdentity.class) { + return false; + } + ServiceIdentity a = (ServiceIdentity) another; + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (publicKeys == null ? a.publicKeys != null : !publicKeys.equals(a.publicKeys)) { + return false; + } + if (providerEndpoint == null ? a.providerEndpoint != null : !providerEndpoint.equals(a.providerEndpoint)) { + return false; + } + if (modified == null ? a.modified != null : !modified.equals(a.modified)) { + return false; + } + if (executable == null ? a.executable != null : !executable.equals(a.executable)) { + return false; + } + if (hosts == null ? a.hosts != null : !hosts.equals(a.hosts)) { + return false; + } + if (user == null ? a.user != null : !user.equals(a.user)) { + return false; + } + if (group == null ? a.group != null : !group.equals(a.group)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentityList.java b/core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentityList.java new file mode 100644 index 00000000000..2c0a027517a --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/ServiceIdentityList.java @@ -0,0 +1,51 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// ServiceIdentityList - The representation for an enumeration of services in +// the namespace, with pagination. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class ServiceIdentityList { + public List names; + @RdlOptional + public String next; + + public ServiceIdentityList setNames(List names) { + this.names = names; + return this; + } + public List getNames() { + return names; + } + public ServiceIdentityList setNext(String next) { + this.next = next; + return this; + } + public String getNext() { + return next; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != ServiceIdentityList.class) { + return false; + } + ServiceIdentityList a = (ServiceIdentityList) another; + if (names == null ? a.names != null : !names.equals(a.names)) { + return false; + } + if (next == null ? a.next != null : !next.equals(a.next)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/ServicePrincipal.java b/core/zms/src/main/java/com/yahoo/athenz/zms/ServicePrincipal.java new file mode 100644 index 00000000000..bfd3939810a --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/ServicePrincipal.java @@ -0,0 +1,59 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// ServicePrincipal - A service principal object identifying a given service. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class ServicePrincipal { + public String domain; + public String service; + public String token; + + public ServicePrincipal setDomain(String domain) { + this.domain = domain; + return this; + } + public String getDomain() { + return domain; + } + public ServicePrincipal setService(String service) { + this.service = service; + return this; + } + public String getService() { + return service; + } + public ServicePrincipal setToken(String token) { + this.token = token; + return this; + } + public String getToken() { + return token; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != ServicePrincipal.class) { + return false; + } + ServicePrincipal a = (ServicePrincipal) another; + if (domain == null ? a.domain != null : !domain.equals(a.domain)) { + return false; + } + if (service == null ? a.service != null : !service.equals(a.service)) { + return false; + } + if (token == null ? a.token != null : !token.equals(a.token)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/SignedDomain.java b/core/zms/src/main/java/com/yahoo/athenz/zms/SignedDomain.java new file mode 100644 index 00000000000..fe8d2c7c899 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/SignedDomain.java @@ -0,0 +1,59 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// SignedDomain - A domain object signed with server's private key +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class SignedDomain { + public DomainData domain; + public String signature; + public String keyId; + + public SignedDomain setDomain(DomainData domain) { + this.domain = domain; + return this; + } + public DomainData getDomain() { + return domain; + } + public SignedDomain setSignature(String signature) { + this.signature = signature; + return this; + } + public String getSignature() { + return signature; + } + public SignedDomain setKeyId(String keyId) { + this.keyId = keyId; + return this; + } + public String getKeyId() { + return keyId; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != SignedDomain.class) { + return false; + } + SignedDomain a = (SignedDomain) another; + if (domain == null ? a.domain != null : !domain.equals(a.domain)) { + return false; + } + if (signature == null ? a.signature != null : !signature.equals(a.signature)) { + return false; + } + if (keyId == null ? a.keyId != null : !keyId.equals(a.keyId)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/SignedDomains.java b/core/zms/src/main/java/com/yahoo/athenz/zms/SignedDomains.java new file mode 100644 index 00000000000..3cbe7656e27 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/SignedDomains.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// SignedDomains - A list of signed domain objects +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class SignedDomains { + public List domains; + + public SignedDomains setDomains(List domains) { + this.domains = domains; + return this; + } + public List getDomains() { + return domains; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != SignedDomains.class) { + return false; + } + SignedDomains a = (SignedDomains) another; + if (domains == null ? a.domains != null : !domains.equals(a.domains)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/SignedPolicies.java b/core/zms/src/main/java/com/yahoo/athenz/zms/SignedPolicies.java new file mode 100644 index 00000000000..05c585df0b0 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/SignedPolicies.java @@ -0,0 +1,60 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// SignedPolicies - A signed bulk transfer of policies. The data is signed with +// server's private key. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class SignedPolicies { + public DomainPolicies contents; + public String signature; + public String keyId; + + public SignedPolicies setContents(DomainPolicies contents) { + this.contents = contents; + return this; + } + public DomainPolicies getContents() { + return contents; + } + public SignedPolicies setSignature(String signature) { + this.signature = signature; + return this; + } + public String getSignature() { + return signature; + } + public SignedPolicies setKeyId(String keyId) { + this.keyId = keyId; + return this; + } + public String getKeyId() { + return keyId; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != SignedPolicies.class) { + return false; + } + SignedPolicies a = (SignedPolicies) another; + if (contents == null ? a.contents != null : !contents.equals(a.contents)) { + return false; + } + if (signature == null ? a.signature != null : !signature.equals(a.signature)) { + return false; + } + if (keyId == null ? a.keyId != null : !keyId.equals(a.keyId)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/SubDomain.java b/core/zms/src/main/java/com/yahoo/athenz/zms/SubDomain.java new file mode 100644 index 00000000000..5b64804a27c --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/SubDomain.java @@ -0,0 +1,144 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// SubDomain - A Subdomain is a TopLevelDomain, except it has a parent. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class SubDomain { + @RdlOptional + public String description; + @RdlOptional + public String org; + @RdlOptional + public Boolean enabled; + @RdlOptional + public Boolean auditEnabled; + @RdlOptional + public String account; + @RdlOptional + public Integer ypmId; + public String name; + public List adminUsers; + @RdlOptional + public DomainTemplateList templates; + public String parent; + + public SubDomain setDescription(String description) { + this.description = description; + return this; + } + public String getDescription() { + return description; + } + public SubDomain setOrg(String org) { + this.org = org; + return this; + } + public String getOrg() { + return org; + } + public SubDomain setEnabled(Boolean enabled) { + this.enabled = enabled; + return this; + } + public Boolean getEnabled() { + return enabled; + } + public SubDomain setAuditEnabled(Boolean auditEnabled) { + this.auditEnabled = auditEnabled; + return this; + } + public Boolean getAuditEnabled() { + return auditEnabled; + } + public SubDomain setAccount(String account) { + this.account = account; + return this; + } + public String getAccount() { + return account; + } + public SubDomain setYpmId(Integer ypmId) { + this.ypmId = ypmId; + return this; + } + public Integer getYpmId() { + return ypmId; + } + public SubDomain setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public SubDomain setAdminUsers(List adminUsers) { + this.adminUsers = adminUsers; + return this; + } + public List getAdminUsers() { + return adminUsers; + } + public SubDomain setTemplates(DomainTemplateList templates) { + this.templates = templates; + return this; + } + public DomainTemplateList getTemplates() { + return templates; + } + public SubDomain setParent(String parent) { + this.parent = parent; + return this; + } + public String getParent() { + return parent; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != SubDomain.class) { + return false; + } + SubDomain a = (SubDomain) another; + if (description == null ? a.description != null : !description.equals(a.description)) { + return false; + } + if (org == null ? a.org != null : !org.equals(a.org)) { + return false; + } + if (enabled == null ? a.enabled != null : !enabled.equals(a.enabled)) { + return false; + } + if (auditEnabled == null ? a.auditEnabled != null : !auditEnabled.equals(a.auditEnabled)) { + return false; + } + if (account == null ? a.account != null : !account.equals(a.account)) { + return false; + } + if (ypmId == null ? a.ypmId != null : !ypmId.equals(a.ypmId)) { + return false; + } + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (adminUsers == null ? a.adminUsers != null : !adminUsers.equals(a.adminUsers)) { + return false; + } + if (templates == null ? a.templates != null : !templates.equals(a.templates)) { + return false; + } + if (parent == null ? a.parent != null : !parent.equals(a.parent)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/Template.java b/core/zms/src/main/java/com/yahoo/athenz/zms/Template.java new file mode 100644 index 00000000000..6803c8413fd --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/Template.java @@ -0,0 +1,49 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Template - Solution Template object defined on the server +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Template { + public List roles; + public List policies; + + public Template setRoles(List roles) { + this.roles = roles; + return this; + } + public List getRoles() { + return roles; + } + public Template setPolicies(List policies) { + this.policies = policies; + return this; + } + public List getPolicies() { + return policies; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Template.class) { + return false; + } + Template a = (Template) another; + if (roles == null ? a.roles != null : !roles.equals(a.roles)) { + return false; + } + if (policies == null ? a.policies != null : !policies.equals(a.policies)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/TemplateList.java b/core/zms/src/main/java/com/yahoo/athenz/zms/TemplateList.java new file mode 100644 index 00000000000..6c59e03d91a --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/TemplateList.java @@ -0,0 +1,39 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// TemplateList - List of template names that is the base struct for server and +// domain templates +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class TemplateList { + public List templateNames; + + public TemplateList setTemplateNames(List templateNames) { + this.templateNames = templateNames; + return this; + } + public List getTemplateNames() { + return templateNames; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != TemplateList.class) { + return false; + } + TemplateList a = (TemplateList) another; + if (templateNames == null ? a.templateNames != null : !templateNames.equals(a.templateNames)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/Tenancy.java b/core/zms/src/main/java/com/yahoo/athenz/zms/Tenancy.java new file mode 100644 index 00000000000..8fc78132985 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/Tenancy.java @@ -0,0 +1,61 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Tenancy - A representation of tenant. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Tenancy { + public String domain; + public String service; + @RdlOptional + public List resourceGroups; + + public Tenancy setDomain(String domain) { + this.domain = domain; + return this; + } + public String getDomain() { + return domain; + } + public Tenancy setService(String service) { + this.service = service; + return this; + } + public String getService() { + return service; + } + public Tenancy setResourceGroups(List resourceGroups) { + this.resourceGroups = resourceGroups; + return this; + } + public List getResourceGroups() { + return resourceGroups; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Tenancy.class) { + return false; + } + Tenancy a = (Tenancy) another; + if (domain == null ? a.domain != null : !domain.equals(a.domain)) { + return false; + } + if (service == null ? a.service != null : !service.equals(a.service)) { + return false; + } + if (resourceGroups == null ? a.resourceGroups != null : !resourceGroups.equals(a.resourceGroups)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/TenancyResourceGroup.java b/core/zms/src/main/java/com/yahoo/athenz/zms/TenancyResourceGroup.java new file mode 100644 index 00000000000..4f564313c93 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/TenancyResourceGroup.java @@ -0,0 +1,59 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// TenancyResourceGroup - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class TenancyResourceGroup { + public String domain; + public String service; + public String resourceGroup; + + public TenancyResourceGroup setDomain(String domain) { + this.domain = domain; + return this; + } + public String getDomain() { + return domain; + } + public TenancyResourceGroup setService(String service) { + this.service = service; + return this; + } + public String getService() { + return service; + } + public TenancyResourceGroup setResourceGroup(String resourceGroup) { + this.resourceGroup = resourceGroup; + return this; + } + public String getResourceGroup() { + return resourceGroup; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != TenancyResourceGroup.class) { + return false; + } + TenancyResourceGroup a = (TenancyResourceGroup) another; + if (domain == null ? a.domain != null : !domain.equals(a.domain)) { + return false; + } + if (service == null ? a.service != null : !service.equals(a.service)) { + return false; + } + if (resourceGroup == null ? a.resourceGroup != null : !resourceGroup.equals(a.resourceGroup)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/TenantDomains.java b/core/zms/src/main/java/com/yahoo/athenz/zms/TenantDomains.java new file mode 100644 index 00000000000..f3a1bd2042d --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/TenantDomains.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// TenantDomains - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class TenantDomains { + public List tenantDomainNames; + + public TenantDomains setTenantDomainNames(List tenantDomainNames) { + this.tenantDomainNames = tenantDomainNames; + return this; + } + public List getTenantDomainNames() { + return tenantDomainNames; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != TenantDomains.class) { + return false; + } + TenantDomains a = (TenantDomains) another; + if (tenantDomainNames == null ? a.tenantDomainNames != null : !tenantDomainNames.equals(a.tenantDomainNames)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/TenantResourceGroupRoles.java b/core/zms/src/main/java/com/yahoo/athenz/zms/TenantResourceGroupRoles.java new file mode 100644 index 00000000000..c30d7e514e2 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/TenantResourceGroupRoles.java @@ -0,0 +1,83 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// TenantResourceGroupRoles - A representation of tenant roles for resource +// groups to be provisioned. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class TenantResourceGroupRoles { + public String domain; + public String service; + public String tenant; + public List roles; + public String resourceGroup; + + public TenantResourceGroupRoles setDomain(String domain) { + this.domain = domain; + return this; + } + public String getDomain() { + return domain; + } + public TenantResourceGroupRoles setService(String service) { + this.service = service; + return this; + } + public String getService() { + return service; + } + public TenantResourceGroupRoles setTenant(String tenant) { + this.tenant = tenant; + return this; + } + public String getTenant() { + return tenant; + } + public TenantResourceGroupRoles setRoles(List roles) { + this.roles = roles; + return this; + } + public List getRoles() { + return roles; + } + public TenantResourceGroupRoles setResourceGroup(String resourceGroup) { + this.resourceGroup = resourceGroup; + return this; + } + public String getResourceGroup() { + return resourceGroup; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != TenantResourceGroupRoles.class) { + return false; + } + TenantResourceGroupRoles a = (TenantResourceGroupRoles) another; + if (domain == null ? a.domain != null : !domain.equals(a.domain)) { + return false; + } + if (service == null ? a.service != null : !service.equals(a.service)) { + return false; + } + if (tenant == null ? a.tenant != null : !tenant.equals(a.tenant)) { + return false; + } + if (roles == null ? a.roles != null : !roles.equals(a.roles)) { + return false; + } + if (resourceGroup == null ? a.resourceGroup != null : !resourceGroup.equals(a.resourceGroup)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/TenantRoleAction.java b/core/zms/src/main/java/com/yahoo/athenz/zms/TenantRoleAction.java new file mode 100644 index 00000000000..2db1f76909d --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/TenantRoleAction.java @@ -0,0 +1,48 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// TenantRoleAction - A representation of tenant role action. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class TenantRoleAction { + public String role; + public String action; + + public TenantRoleAction setRole(String role) { + this.role = role; + return this; + } + public String getRole() { + return role; + } + public TenantRoleAction setAction(String action) { + this.action = action; + return this; + } + public String getAction() { + return action; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != TenantRoleAction.class) { + return false; + } + TenantRoleAction a = (TenantRoleAction) another; + if (role == null ? a.role != null : !role.equals(a.role)) { + return false; + } + if (action == null ? a.action != null : !action.equals(a.action)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/TenantRoles.java b/core/zms/src/main/java/com/yahoo/athenz/zms/TenantRoles.java new file mode 100644 index 00000000000..0c77b73d565 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/TenantRoles.java @@ -0,0 +1,71 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// TenantRoles - A representation of tenant roles to be provisioned. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class TenantRoles { + public String domain; + public String service; + public String tenant; + public List roles; + + public TenantRoles setDomain(String domain) { + this.domain = domain; + return this; + } + public String getDomain() { + return domain; + } + public TenantRoles setService(String service) { + this.service = service; + return this; + } + public String getService() { + return service; + } + public TenantRoles setTenant(String tenant) { + this.tenant = tenant; + return this; + } + public String getTenant() { + return tenant; + } + public TenantRoles setRoles(List roles) { + this.roles = roles; + return this; + } + public List getRoles() { + return roles; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != TenantRoles.class) { + return false; + } + TenantRoles a = (TenantRoles) another; + if (domain == null ? a.domain != null : !domain.equals(a.domain)) { + return false; + } + if (service == null ? a.service != null : !service.equals(a.service)) { + return false; + } + if (tenant == null ? a.tenant != null : !tenant.equals(a.tenant)) { + return false; + } + if (roles == null ? a.roles != null : !roles.equals(a.roles)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/TopLevelDomain.java b/core/zms/src/main/java/com/yahoo/athenz/zms/TopLevelDomain.java new file mode 100644 index 00000000000..f07b1191e2f --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/TopLevelDomain.java @@ -0,0 +1,134 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// TopLevelDomain - Top Level Domain object. The required attributes include +// the name of the domain and list of domain administrators. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class TopLevelDomain { + @RdlOptional + public String description; + @RdlOptional + public String org; + @RdlOptional + public Boolean enabled; + @RdlOptional + public Boolean auditEnabled; + @RdlOptional + public String account; + @RdlOptional + public Integer ypmId; + public String name; + public List adminUsers; + @RdlOptional + public DomainTemplateList templates; + + public TopLevelDomain setDescription(String description) { + this.description = description; + return this; + } + public String getDescription() { + return description; + } + public TopLevelDomain setOrg(String org) { + this.org = org; + return this; + } + public String getOrg() { + return org; + } + public TopLevelDomain setEnabled(Boolean enabled) { + this.enabled = enabled; + return this; + } + public Boolean getEnabled() { + return enabled; + } + public TopLevelDomain setAuditEnabled(Boolean auditEnabled) { + this.auditEnabled = auditEnabled; + return this; + } + public Boolean getAuditEnabled() { + return auditEnabled; + } + public TopLevelDomain setAccount(String account) { + this.account = account; + return this; + } + public String getAccount() { + return account; + } + public TopLevelDomain setYpmId(Integer ypmId) { + this.ypmId = ypmId; + return this; + } + public Integer getYpmId() { + return ypmId; + } + public TopLevelDomain setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public TopLevelDomain setAdminUsers(List adminUsers) { + this.adminUsers = adminUsers; + return this; + } + public List getAdminUsers() { + return adminUsers; + } + public TopLevelDomain setTemplates(DomainTemplateList templates) { + this.templates = templates; + return this; + } + public DomainTemplateList getTemplates() { + return templates; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != TopLevelDomain.class) { + return false; + } + TopLevelDomain a = (TopLevelDomain) another; + if (description == null ? a.description != null : !description.equals(a.description)) { + return false; + } + if (org == null ? a.org != null : !org.equals(a.org)) { + return false; + } + if (enabled == null ? a.enabled != null : !enabled.equals(a.enabled)) { + return false; + } + if (auditEnabled == null ? a.auditEnabled != null : !auditEnabled.equals(a.auditEnabled)) { + return false; + } + if (account == null ? a.account != null : !account.equals(a.account)) { + return false; + } + if (ypmId == null ? a.ypmId != null : !ypmId.equals(a.ypmId)) { + return false; + } + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (adminUsers == null ? a.adminUsers != null : !adminUsers.equals(a.adminUsers)) { + return false; + } + if (templates == null ? a.templates != null : !templates.equals(a.templates)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/UserDomain.java b/core/zms/src/main/java/com/yahoo/athenz/zms/UserDomain.java new file mode 100644 index 00000000000..6b837bfafcc --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/UserDomain.java @@ -0,0 +1,122 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// UserDomain - A UserDomain is the user's own top level domain in user - e.g. +// user.hga +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class UserDomain { + @RdlOptional + public String description; + @RdlOptional + public String org; + @RdlOptional + public Boolean enabled; + @RdlOptional + public Boolean auditEnabled; + @RdlOptional + public String account; + @RdlOptional + public Integer ypmId; + public String name; + @RdlOptional + public DomainTemplateList templates; + + public UserDomain setDescription(String description) { + this.description = description; + return this; + } + public String getDescription() { + return description; + } + public UserDomain setOrg(String org) { + this.org = org; + return this; + } + public String getOrg() { + return org; + } + public UserDomain setEnabled(Boolean enabled) { + this.enabled = enabled; + return this; + } + public Boolean getEnabled() { + return enabled; + } + public UserDomain setAuditEnabled(Boolean auditEnabled) { + this.auditEnabled = auditEnabled; + return this; + } + public Boolean getAuditEnabled() { + return auditEnabled; + } + public UserDomain setAccount(String account) { + this.account = account; + return this; + } + public String getAccount() { + return account; + } + public UserDomain setYpmId(Integer ypmId) { + this.ypmId = ypmId; + return this; + } + public Integer getYpmId() { + return ypmId; + } + public UserDomain setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public UserDomain setTemplates(DomainTemplateList templates) { + this.templates = templates; + return this; + } + public DomainTemplateList getTemplates() { + return templates; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != UserDomain.class) { + return false; + } + UserDomain a = (UserDomain) another; + if (description == null ? a.description != null : !description.equals(a.description)) { + return false; + } + if (org == null ? a.org != null : !org.equals(a.org)) { + return false; + } + if (enabled == null ? a.enabled != null : !enabled.equals(a.enabled)) { + return false; + } + if (auditEnabled == null ? a.auditEnabled != null : !auditEnabled.equals(a.auditEnabled)) { + return false; + } + if (account == null ? a.account != null : !account.equals(a.account)) { + return false; + } + if (ypmId == null ? a.ypmId != null : !ypmId.equals(a.ypmId)) { + return false; + } + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (templates == null ? a.templates != null : !templates.equals(a.templates)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/UserToken.java b/core/zms/src/main/java/com/yahoo/athenz/zms/UserToken.java new file mode 100644 index 00000000000..e794577a539 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/UserToken.java @@ -0,0 +1,37 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// UserToken - A user token generated based on user's credentials +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class UserToken { + public String token; + + public UserToken setToken(String token) { + this.token = token; + return this; + } + public String getToken() { + return token; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != UserToken.class) { + return false; + } + UserToken a = (UserToken) another; + if (token == null ? a.token != null : !token.equals(a.token)) { + return false; + } + } + return true; + } +} diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java b/core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java new file mode 100644 index 00000000000..f9f7209b474 --- /dev/null +++ b/core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java @@ -0,0 +1,1413 @@ +// +// This file generated by rdl 1.4.8 +// + +package com.yahoo.athenz.zms; +import com.yahoo.rdl.*; + +public class ZMSSchema { + + private final static Schema INSTANCE = build(); + public static Schema instance() { + return INSTANCE; + } + + private static Schema build() { + SchemaBuilder sb = new SchemaBuilder("ZMS"); + sb.version(1); + sb.namespace("com.yahoo.athenz.zms"); + sb.comment("Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. The Authorization Management Service (ZMS) Classes"); + + sb.stringType("SimpleName") + .comment("Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. Common name types used by several API definitions A simple identifier, an element of compound name.") + .pattern("[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("CompoundName") + .comment("A compound name. Most names in this API are compound names.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("DomainName") + .comment("A domain name is the general qualifier prefix, as its uniqueness is managed.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("EntityName") + .comment("An entity name is a short form of a resource name, including only the domain and entity.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("ServiceName") + .comment("A service name will generally be a unique subdomain.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("LocationName") + .comment("A location name is not yet defined, but will be a dotted name like everything else.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("ActionName") + .comment("An action (operation) name.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("ResourceName") + .comment("A shorthand for a YRN with no service or location. The 'tail' of a YRN, just the domain:entity. Note that the EntityName part is optional, that is, a domain name followed by a colon is valid resource name.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*(:([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?"); + + sb.stringType("YRN") + .comment("A full Yahoo Resource name (YRN).") + .pattern("(yrn:(([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?:(([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?:)?([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*(:([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?"); + + sb.stringType("YBase64") + .comment("The Y-specific URL-safe Base64 variant.") + .pattern("[a-zA-Z0-9\\._-]+"); + + sb.stringType("YEncoded") + .comment("YEncoded includes ybase64 chars, as well as = and %. This can represent a user cookie and URL-encoded values.") + .pattern("[a-zA-Z0-9\\._%=-]*"); + + sb.stringType("AuthorityName") + .comment("Used as the prefix in a signed assertion. This uniquely identifies a signing authority. i.e. \"user\"") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("SignedToken") + .comment("A signed assertion if identity. i.e. the user cookie value. This token will only make sense to the authority that generated it, so it is beneficial to have something in the value that is cheaply recognized to quickly reject if it belongs to another authority. In addition to the YEncoded set our token includes ; to separate components and , to separate roles and : for IPv6 addresses") + .pattern("[a-zA-Z0-9\\._%=:;,-]*"); + + sb.structType("Domain") + .comment("A domain is an independent partition of users, roles, and resources. Its name represents the definition of a namespace; the only way a new namespace can be created, from the top, is by creating Domains. Administration of a domain is governed by the parent domain (using reverse-DNS namespaces). The top level domains are governed by the special \"sys.auth\" domain.") + .field("name", "DomainName", false, "the common name to be referred to, the symbolic id. It is immutable") + .field("modified", "Timestamp", true, "the last modification timestamp of any object or attribute in this domain") + .field("id", "UUID", true, "unique identifier of the domain. generated on create, never reused") + .field("description", "String", true, "description of the domain") + .field("org", "ResourceName", true, "a reference to an Organization") + .field("enabled", "Bool", false, "Future use only, currently not used", true) + .field("auditEnabled", "Bool", false, "Flag indicates whether or not domain modifications should be logged for SOX+Auditing. If true, the auditRef parameter must be supplied(not empty) for any API defining it.", false) + .field("account", "String", true, "associated cloud (i.e. aws) account id") + .field("ypmId", "Int32", true, "associated product id"); + + sb.structType("RoleList") + .comment("The representation for an enumeration of roles in the namespace, with pagination.") + .arrayField("names", "EntityName", false, "list of role names") + .field("next", "String", true, "if the response is a paginated list, this attribute specifies the value to be used in the next role list request as the value for the skip query parameter."); + + sb.structType("RoleAuditLog") + .comment("An audit log entry for role membership change.") + .field("member", "ResourceName", false, "name of the role member") + .field("admin", "ResourceName", false, "name of the principal executing the change") + .field("created", "Timestamp", false, "timestamp of the entry") + .field("action", "String", false, "log action - either add or delete") + .field("auditRef", "String", true, "audit reference string for the change as supplied by admin"); + + sb.structType("Role") + .comment("The representation for a Role with set of members.") + .field("name", "ResourceName", false, "name of the role") + .field("modified", "Timestamp", true, "last modification timestamp of the role") + .arrayField("members", "ResourceName", true, "an explicit list of members. Might be empty or null, if trust is set") + .field("trust", "DomainName", true, "a trusted domain to delegate membership decisions to") + .arrayField("auditLog", "RoleAuditLog", true, "an audit log for role membership changes"); + + sb.structType("Roles") + .comment("The representation for a list of roles with full details") + .arrayField("list", "Role", false, "list of role objects"); + + sb.structType("Membership") + .comment("The representation for a role membership.") + .field("memberName", "ResourceName", false, "name of the member") + .field("isMember", "Bool", false, "flag to indicate whether or the user is a member or not", true) + .field("roleName", "ResourceName", true, "name of the role"); + + sb.structType("DefaultAdmins") + .comment("The list of domain administrators.") + .arrayField("admins", "ResourceName", false, "list of domain administrators"); + + sb.enumType("AssertionEffect") + .comment("Every assertion can have the effect of ALLOW or DENY.") + .element("ALLOW") + .element("DENY"); + + sb.structType("Assertion") + .comment("A representation for the encapsulation of an action to be performed on a resource by a principal.") + .field("role", "String", false, "the subject of the assertion - a role") + .field("resource", "String", false, "the object of the assertion. Must be in the local namespace. Can contain wildcards") + .field("action", "String", false, "the predicate of the assertion. Can contain wildcards") + .field("effect", "AssertionEffect", false, "the effect of the assertion in the policy language", null) + .field("id", "Int64", true, "assertion id - auto generated by server. Not required during put operations."); + + sb.structType("Policy") + .comment("The representation for a Policy with set of assertions.") + .field("name", "ResourceName", false, "name of the policy") + .field("modified", "Timestamp", true, "last modification timestamp of this policy") + .arrayField("assertions", "Assertion", false, "list of defined assertions for this policy"); + + sb.structType("Policies") + .comment("The representation of list of policy objects") + .arrayField("list", "Policy", false, "list of policy objects"); + + sb.structType("Template") + .comment("Solution Template object defined on the server") + .arrayField("roles", "Role", false, "list of roles in the template") + .arrayField("policies", "Policy", false, "list of policies defined in this template"); + + sb.structType("TemplateList") + .comment("List of template names that is the base struct for server and domain templates") + .arrayField("templateNames", "SimpleName", false, "list of template names"); + + sb.structType("DomainTemplate", "TemplateList") + .comment("solution template(s) to be applied to a domain"); + + sb.structType("DomainTemplateList", "TemplateList") + .comment("List of solution templates to be applied to a domain"); + + sb.structType("ServerTemplateList", "TemplateList") + .comment("List of solution templates available in the server"); + + sb.structType("DomainList") + .comment("A paginated list of domains.") + .arrayField("names", "DomainName", false, "list of domain names") + .field("next", "String", true, "if the response is a paginated list, this attribute specifies the value to be used in the next domain list request as the value for the skip query parameter."); + + sb.structType("DomainMeta") + .comment("Set of metadata attributes that all domains may have and can be changed.") + .field("description", "String", true, "a description of the domain") + .field("org", "ResourceName", true, "a reference to an Organization. (i.e. org:media)") + .field("enabled", "Bool", false, "Future use only, currently not used", true) + .field("auditEnabled", "Bool", false, "Flag indicates whether or not domain modifications should be logged for SOX+Auditing. If true, the auditRef parameter must be supplied(not empty) for any API defining it.", false) + .field("account", "String", true, "associated cloud (i.e. aws) account id") + .field("ypmId", "Int32", true, "associated product id"); + + sb.structType("TopLevelDomain", "DomainMeta") + .comment("Top Level Domain object. The required attributes include the name of the domain and list of domain administrators.") + .field("name", "SimpleName", false, "name of the domain") + .arrayField("adminUsers", "ResourceName", false, "list of domain administrators") + .field("templates", "DomainTemplateList", true, "list of solution template names"); + + sb.structType("SubDomain", "TopLevelDomain") + .comment("A Subdomain is a TopLevelDomain, except it has a parent.") + .field("parent", "DomainName", false, "name of the parent domain"); + + sb.structType("UserDomain", "DomainMeta") + .comment("A UserDomain is the user's own top level domain in user - e.g. user.hga") + .field("name", "SimpleName", false, "user id which will be the domain name") + .field("templates", "DomainTemplateList", true, "list of solution template names"); + + sb.structType("DanglingPolicy") + .comment("A dangling policy where the assertion is referencing a role name that doesn't exist in the domain") + .field("policyName", "EntityName", false, "") + .field("roleName", "EntityName", false, ""); + + sb.structType("DomainDataCheck") + .comment("Domain data object representing the results of a check operation looking for dangling roles, policies and trust relationships that are set either on tenant or provider side only") + .arrayField("danglingRoles", "EntityName", true, "Names of roles not specified in any assertion. Might be empty or null if no dangling roles.") + .arrayField("danglingPolicies", "DanglingPolicy", true, "Policy+role tuples where role doesnt exist. Might be empty or null if no dangling policies.") + .field("policyCount", "Int32", false, "total number of policies") + .field("assertionCount", "Int32", false, "total number of assertions") + .field("roleWildCardCount", "Int32", false, "total number of assertions containing roles as wildcards") + .arrayField("providersWithoutTrust", "ServiceName", true, "Service names (domain.service) that dont contain trust role if this is a tenant domain. Might be empty or null, if not a tenant or if all providers support this tenant.") + .arrayField("tenantsWithoutAssumeRole", "DomainName", true, "Names of Tenant domains that dont contain assume role assertions if this is a provider domain. Might be empty or null, if not a provider or if all tenants support use this provider."); + + sb.structType("Entity") + .comment("An entity is a name and a structured value. some entity names/prefixes are reserved (i.e. \"role\", \"policy\", \"meta\", \"domain\", \"service\")") + .field("name", "EntityName", false, "name of the entity object") + .field("value", "Struct", false, "value of the entity"); + + sb.structType("EntityList") + .comment("The representation for an enumeration of entities in the namespace") + .arrayField("names", "EntityName", false, "list of entity names"); + + sb.structType("PolicyList") + .comment("The representation for an enumeration of policies in the namespace, with pagination.") + .arrayField("names", "EntityName", false, "list of policy names") + .field("next", "String", true, "if the response is a paginated list, this attribute specifies the value to be used in the next policy list request as the value for the skip query parameter."); + + sb.structType("PublicKeyEntry") + .comment("The representation of the public key in a service identity object.") + .field("key", "String", false, "the public key for the service") + .field("id", "String", false, "the key identifier (version or zone name)"); + + sb.structType("ServiceIdentity") + .comment("The representation of the service identity object.") + .field("name", "ServiceName", false, "the full name of the service, i.e. \"sports.storage\"") + .arrayField("publicKeys", "PublicKeyEntry", true, "array of public keys for key rotation") + .field("providerEndpoint", "String", true, "if present, then this service can provision tenants via this endpoint.") + .field("modified", "Timestamp", true, "the timestamp when this entry was last modified") + .field("executable", "String", true, "the path of the executable that runs the service") + .arrayField("hosts", "String", true, "list of host names that this service can run on") + .field("user", "String", true, "local (unix) user name this service can run as") + .field("group", "String", true, "local (unix) group name this service can run as"); + + sb.structType("ServiceIdentities") + .comment("The representation of list of services") + .arrayField("list", "ServiceIdentity", false, "list of services"); + + sb.structType("ServiceIdentityList") + .comment("The representation for an enumeration of services in the namespace, with pagination.") + .arrayField("names", "EntityName", false, "list of service names") + .field("next", "String", true, "if the response is a paginated list, this attribute specifies the value to be used in the next service list request as the value for the skip query parameter."); + + sb.structType("Tenancy") + .comment("A representation of tenant.") + .field("domain", "DomainName", false, "the domain that is to get a tenancy") + .field("service", "ServiceName", false, "the provider service on which the tenancy is to reside") + .arrayField("resourceGroups", "EntityName", true, "registered resource groups for this tenant"); + + sb.structType("TenancyResourceGroup") + .field("domain", "DomainName", false, "the domain that is to get a tenancy") + .field("service", "ServiceName", false, "the provider service on which the tenancy is to reside") + .field("resourceGroup", "EntityName", false, "registered resource group for this tenant"); + + sb.structType("TenantRoleAction") + .comment("A representation of tenant role action.") + .field("role", "SimpleName", false, "name of the role") + .field("action", "String", false, "action value for the generated policy assertion"); + + sb.structType("TenantRoles") + .comment("A representation of tenant roles to be provisioned.") + .field("domain", "DomainName", false, "name of the provider domain") + .field("service", "SimpleName", false, "name of the provider service") + .field("tenant", "DomainName", false, "name of the tenant domain") + .arrayField("roles", "TenantRoleAction", false, "the role/action pairs to provision"); + + sb.structType("TenantResourceGroupRoles") + .comment("A representation of tenant roles for resource groups to be provisioned.") + .field("domain", "DomainName", false, "name of the provider domain") + .field("service", "SimpleName", false, "name of the provider service") + .field("tenant", "DomainName", false, "name of the tenant domain") + .arrayField("roles", "TenantRoleAction", false, "the role/action pairs to provision") + .field("resourceGroup", "EntityName", false, "tenant resource group"); + + sb.structType("ProviderResourceGroupRoles") + .comment("A representation of provider roles to be provisioned.") + .field("domain", "DomainName", false, "name of the provider domain") + .field("service", "SimpleName", false, "name of the provider service") + .field("tenant", "DomainName", false, "name of the tenant domain") + .arrayField("roles", "TenantRoleAction", false, "the role/action pairs to provision") + .field("resourceGroup", "EntityName", false, "tenant resource group"); + + sb.structType("Access") + .comment("Access can be checked and returned as this resource.") + .field("granted", "Bool", false, "true (allowed) or false (denied)"); + + sb.structType("ResourceAccess") + .field("principal", "EntityName", false, "") + .arrayField("assertions", "Assertion", false, ""); + + sb.structType("ResourceAccessList") + .arrayField("resources", "ResourceAccess", false, ""); + + sb.structType("DomainModified") + .comment("Tuple of domain-name and modification time-stamps. This object is returned when the caller has requested list of domains modified since a specific timestamp.") + .field("name", "DomainName", false, "name of the domain") + .field("modified", "Int64", false, "last modified timestamp of the domain"); + + sb.structType("DomainModifiedList") + .comment("A list of {domain, modified-timestamp} tuples.") + .arrayField("nameModList", "DomainModified", false, "list of modified domains"); + + sb.structType("DomainPolicies") + .comment("We need to include the name of the domain in this struct since this data will be passed back to ZPU through ZTS so we need to sign not only the list of policies but also the corresponding domain name that the policies belong to.") + .field("domain", "DomainName", false, "name of the domain") + .arrayField("policies", "Policy", false, "list of policies defined in this server"); + + sb.structType("SignedPolicies") + .comment("A signed bulk transfer of policies. The data is signed with server's private key.") + .field("contents", "DomainPolicies", false, "list of policies defined in a domain") + .field("signature", "String", false, "signature generated based on the domain policies object") + .field("keyId", "String", false, "the identifier of the key used to generate the signature"); + + sb.structType("DomainData") + .comment("A domain object that includes its roles, policies and services.") + .field("name", "DomainName", false, "name of the domain") + .field("account", "String", true, "associated cloud (i.e. aws) account id") + .field("ypmId", "Int32", true, "associated product id") + .arrayField("roles", "Role", false, "list of roles in the domain") + .field("policies", "SignedPolicies", false, "list of policies in the domain signed with ZMS private key") + .arrayField("services", "ServiceIdentity", false, "list of services in the domain") + .arrayField("entities", "Entity", false, "list of entities in the domain") + .field("modified", "Timestamp", false, "last modification timestamp"); + + sb.structType("SignedDomain") + .comment("A domain object signed with server's private key") + .field("domain", "DomainData", false, "domain object with its roles, policies and services") + .field("signature", "String", false, "signature generated based on the domain object") + .field("keyId", "String", false, "the identifier of the key used to generate the signature"); + + sb.structType("SignedDomains") + .comment("A list of signed domain objects") + .arrayField("domains", "SignedDomain", false, ""); + + sb.structType("UserToken") + .comment("A user token generated based on user's credentials") + .field("token", "SignedToken", false, "Signed user token identifying a specific authenticated user"); + + sb.structType("ServicePrincipal") + .comment("A service principal object identifying a given service.") + .field("domain", "DomainName", false, "name of the domain") + .field("service", "EntityName", false, "name of the service") + .field("token", "SignedToken", false, "service's signed token"); + + + sb.resource("Domain", "GET", "/domain/{domain}") + .comment("Get info for the specified domain, by name. This request only returns the configured domain attributes and not any domain objects like roles, policies or service identities.") + .pathParam("domain", "DomainName", "name of the domain") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("DomainList", "GET", "/domain") + .comment("Enumerate domains. Can be filtered by prefix and depth, and paginated. This operation can be expensive, as it may span multiple domains.") + .queryParam("limit", "limit", "Int32", null, "restrict the number of results in this call") + .queryParam("skip", "skip", "String", null, "restrict the set to those after the specified \"next\" token returned from a previous call") + .queryParam("prefix", "prefix", "String", null, "restrict to names that start with the prefix") + .queryParam("depth", "depth", "Int32", null, "restrict the depth of the name, specifying the number of '.' characters that can appear") + .queryParam("account", "account", "String", null, "restrict to domain names that have specified account name") + .queryParam("ypmid", "productId", "Int32", null, "restrict the domain names that have specified product id") + .queryParam("member", "roleMember", "ResourceName", null, "restrict the domain names where the specified user is in a role - see roleName") + .queryParam("role", "roleName", "ResourceName", null, "restrict the domain names where the specified user is in this role - see roleMember") + .headerParam("If-Modified-Since", "modifiedSince", "String", null, "This header specifies to the server to return any domains modified since this HTTP date") + .expected("OK") + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TopLevelDomain", "POST", "/domain") + .comment("Create a new top level domain. This is a privileged action for the \"sys.auth\" administrators.") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("detail", "TopLevelDomain", "TopLevelDomain object to be created") + .auth("create", "sys.auth:domain") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("SubDomain", "POST", "/subdomain/{parent}") + .comment("Create a new subdomain. The domain administrators of the {parent} domain have the privilege to create subdomains.") + .pathParam("parent", "DomainName", "name of the parent domain") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("detail", "SubDomain", "Subdomain object to be created") + .auth("create", "{parent}:domain") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("UserDomain", "POST", "/userdomain/{name}") + .comment("Create a new user domain. The user domain will be created in the user top level domain and the user himself will be set as the administrator for this domain.") + .pathParam("name", "SimpleName", "name of the domain which will be the user id") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("detail", "UserDomain", "UserDomain object to be created") + .auth("create", "user.{name}:domain") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TopLevelDomain", "DELETE", "/domain/{name}") + .comment("Delete the specified domain. This is a privileged action for the \"sys.auth\" administrators. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("name", "DomainName", "name of the domain to be deleted") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("delete", "sys.auth:domain") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("SubDomain", "DELETE", "/subdomain/{parent}/{name}") + .comment("Delete the specified subdomain. Caller must have domain delete permissions in parent. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("parent", "DomainName", "name of the parent domain") + .pathParam("name", "DomainName", "name of the subdomain to be deleted") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("delete", "{parent}:domain") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("UserDomain", "DELETE", "/userdomain/{name}") + .comment("Delete the specified userdomain. Caller must have domain delete permissions in the domain. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("name", "SimpleName", "name of the domain to be deleted which will be the user id") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("delete", "user.{name}:domain") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("DomainMeta", "PUT", "/domain/{name}/meta") + .comment("Update the specified top level domain metadata. Note that entities in the domain are not affected. Caller must have update privileges on the domain itself.") + .pathParam("name", "DomainName", "name of the domain to be updated") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("detail", "DomainMeta", "DomainMeta object with updated attribute values") + .auth("update", "{name}:") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("DomainTemplate", "PUT", "/domain/{name}/template") + .comment("Update the given domain by applying the roles and policies defined in the specified solution template(s). Caller must have UPDATE privileges on the domain itself.") + .pathParam("name", "DomainName", "name of the domain to be updated") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("template", "DomainTemplate", "DomainTemplate object with solution template name(s)") + .auth("update", "{name}:") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("DomainTemplateList", "GET", "/domain/{name}/template") + .comment("Get the list of solution templates applied to a domain") + .pathParam("name", "DomainName", "name of the domain") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("DomainTemplate", "DELETE", "/domain/{name}/template/{template}") + .comment("Update the given domain by deleting the specified template from the domain template list. Cycles through the roles and policies defined in the template and deletes them. Caller must have delete privileges on the domain itself.") + .pathParam("name", "DomainName", "name of the domain to be updated") + .pathParam("template", "SimpleName", "name of the solution template") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("delete", "{name}:") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("DomainDataCheck", "GET", "/domain/{domainName}/check") + .comment("Carry out data check operation for the specified domain.") + .pathParam("domainName", "DomainName", "name of the domain") + .auth("", "", true) + .expected("OK") + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Entity", "PUT", "/domain/{domainName}/entity/{entityName}") + .comment("Put an entity into the domain.") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("entityName", "EntityName", "name of entity") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("entity", "Entity", "Entity object to be added to the domain") + .auth("update", "{domainName}:{entityName}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Entity", "GET", "/domain/{domainName}/entity/{entityName}") + .comment("Get a entity from a domain. open for all authenticated users to read") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("entityName", "EntityName", "name of entity") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Entity", "DELETE", "/domain/{domainName}/entity/{entityName}") + .comment("Delete the entity from the domain. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("entityName", "EntityName", "name of entity") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("delete", "{domainName}:{entityName}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("EntityList", "GET", "/domain/{domainName}/entity") + .comment("Enumerate entities provisioned in this domain.") + .pathParam("domainName", "DomainName", "name of the domain") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("RoleList", "GET", "/domain/{domainName}/role") + .comment("Enumerate roles provisioned in this domain.") + .pathParam("domainName", "DomainName", "name of the domain") + .queryParam("limit", "limit", "Int32", null, "restrict the number of results in this call") + .queryParam("skip", "skip", "String", null, "restrict the set to those after the specified \"next\" token returned from a previous call") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Roles", "GET", "/domain/{domainName}/roles") + .comment("Get the list of all roles in a domain with optional flag whether or not include members") + .pathParam("domainName", "DomainName", "name of the domain") + .queryParam("members", "members", "Bool", false, "return list of members in the role") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Role", "GET", "/domain/{domainName}/role/{roleName}") + .comment("Get the specified role in the domain.") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("roleName", "EntityName", "name of the role to be retrieved") + .queryParam("auditLog", "auditLog", "Bool", false, "flag to indicate whether or not to return role audit log") + .queryParam("expand", "expand", "Bool", false, "expand delegated trust roles and return trusted members") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Role", "PUT", "/domain/{domainName}/role/{roleName}") + .comment("Create/update the specified role.") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("roleName", "EntityName", "name of the role to be added/updated") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("role", "Role", "Role object to be added/updated in the domain") + .auth("update", "{domainName}:role.{roleName}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Role", "DELETE", "/domain/{domainName}/role/{roleName}") + .comment("Delete the specified role. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("roleName", "EntityName", "name of the role to be deleted") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("delete", "{domainName}:role.{roleName}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Membership", "GET", "/domain/{domainName}/role/{roleName}/member/{memberName}") + .comment("Get the membership status for a specified user in a role.") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("roleName", "EntityName", "name of the role") + .pathParam("memberName", "ResourceName", "user name to be checked for membership") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Membership", "PUT", "/domain/{domainName}/role/{roleName}/member/{memberName}") + .comment("Add the specified user to the role's member list.") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("roleName", "EntityName", "name of the role") + .pathParam("memberName", "ResourceName", "name of the user to be added as a member") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("membership", "Membership", "Membership object (must contain role/member names as specified in the URI)") + .auth("update", "{domainName}:role.{roleName}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Membership", "DELETE", "/domain/{domainName}/role/{roleName}/member/{memberName}") + .comment("Delete the specified role membership. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("roleName", "EntityName", "name of the role") + .pathParam("memberName", "ResourceName", "name of the user to be removed as a member") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("update", "{domainName}:role.{roleName}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("DefaultAdmins", "PUT", "/domain/{domainName}/admins") + .comment("Verify and, if necessary, fix domain roles and policies to make sure the given set of users have administrative access to the domain. This request is only restricted to \"sys.auth\" domain administrators and can be used when the domain administrators incorrectly have blocked their own access to their domains.") + .pathParam("domainName", "DomainName", "name of the domain") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("defaultAdmins", "DefaultAdmins", "list of domain administrators") + .auth("update", "sys.auth:domain") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("PolicyList", "GET", "/domain/{domainName}/policy") + .comment("List policies provisioned in this namespace.") + .pathParam("domainName", "DomainName", "name of the domain") + .queryParam("limit", "limit", "Int32", null, "restrict the number of results in this call") + .queryParam("skip", "skip", "String", null, "restrict the set to those after the specified \"next\" token returned from a previous call") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Policies", "GET", "/domain/{domainName}/policies") + .comment("List policies provisioned in this namespace.") + .pathParam("domainName", "DomainName", "name of the domain") + .queryParam("assertions", "assertions", "Bool", false, "return list of assertions in the policy") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Policy", "GET", "/domain/{domainName}/policy/{policyName}") + .comment("Read the specified policy.") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("policyName", "EntityName", "name of the policy to be retrieved") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Policy", "PUT", "/domain/{domainName}/policy/{policyName}") + .comment("Create or update the specified policy.") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("policyName", "EntityName", "name of the policy to be added/updated") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("policy", "Policy", "Policy object to be added or updated in the domain") + .auth("update", "{domainName}:policy.{policyName}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Policy", "DELETE", "/domain/{domainName}/policy/{policyName}") + .comment("Delete the specified policy. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("policyName", "EntityName", "name of the policy to be deleted") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("delete", "{domainName}:policy.{policyName}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Assertion", "GET", "/domain/{domainName}/policy/{policyName}/assertion/{assertionId}") + .comment("Get the assertion details with specified id in the given policy") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("policyName", "EntityName", "name of the policy") + .pathParam("assertionId", "Int64", "assertion id") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Assertion", "PUT", "/domain/{domainName}/policy/{policyName}/assertion") + .comment("Add the specified assertion to the given policy") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("policyName", "EntityName", "name of the policy") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("assertion", "Assertion", "Assertion object to be added to the given policy") + .auth("update", "{domainName}:policy.{policyName}") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Assertion", "DELETE", "/domain/{domainName}/policy/{policyName}/assertion/{assertionId}") + .comment("Delete the specified policy assertion. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("policyName", "EntityName", "name of the policy") + .pathParam("assertionId", "Int64", "assertion id") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("update", "{domainName}:policy.{policyName}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("ServiceIdentity", "PUT", "/domain/{domain}/service/{service}") + .comment("Register the specified ServiceIdentity in the specified domain") + .pathParam("domain", "DomainName", "name of the domain") + .pathParam("service", "SimpleName", "name of the service") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("detail", "ServiceIdentity", "ServiceIdentity object to be added/updated in the domain") + .auth("update", "{domain}:service") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("ServiceIdentity", "GET", "/domain/{domain}/service/{service}") + .comment("Get info for the specified ServiceIdentity.") + .pathParam("domain", "DomainName", "name of the domain") + .pathParam("service", "SimpleName", "name of the service to be retrieved") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("ServiceIdentity", "DELETE", "/domain/{domain}/service/{service}") + .comment("Delete the specified ServiceIdentity. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("domain", "DomainName", "name of the domain") + .pathParam("service", "SimpleName", "name of the service to be deleted") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("delete", "{domain}:service") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("ServiceIdentities", "GET", "/domain/{domainName}/services") + .comment("Retrieve list of service identities") + .pathParam("domainName", "DomainName", "name of the domain") + .queryParam("publickeys", "publickeys", "Bool", false, "return list of public keys in the service") + .queryParam("hosts", "hosts", "Bool", false, "return list of hosts in the service") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("ServiceIdentityList", "GET", "/domain/{domainName}/service") + .comment("Enumerate services provisioned in this domain.") + .pathParam("domainName", "DomainName", "name of the domain") + .queryParam("limit", "limit", "Int32", null, "restrict the number of results in this call") + .queryParam("skip", "skip", "String", null, "restrict the set to those after the specified \"next\" token returned from a previous call") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("PublicKeyEntry", "GET", "/domain/{domain}/service/{service}/publickey/{id}") + .comment("Retrieve the specified public key from the service.") + .pathParam("domain", "DomainName", "name of the domain") + .pathParam("service", "SimpleName", "name of the service") + .pathParam("id", "String", "the identifier of the public key to be retrieved") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("PublicKeyEntry", "PUT", "/domain/{domain}/service/{service}/publickey/{id}") + .comment("Add the specified public key to the service.") + .pathParam("domain", "DomainName", "name of the domain") + .pathParam("service", "SimpleName", "name of the service") + .pathParam("id", "String", "the identifier of the public key to be added") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("publicKeyEntry", "PublicKeyEntry", "PublicKeyEntry object to be added/updated in the service") + .auth("update", "{domain}:service.{service}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("PublicKeyEntry", "DELETE", "/domain/{domain}/service/{service}/publickey/{id}") + .comment("Remove the specified public key from the service. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("domain", "DomainName", "name of the domain") + .pathParam("service", "SimpleName", "name of the service") + .pathParam("id", "String", "the identifier of the public key to be deleted") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("update", "{domain}:service.{service}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Tenancy", "PUT", "/domain/{domain}/tenancy/{service}") + .comment("Add a tenant for the specified service.") + .pathParam("domain", "DomainName", "name of the tenant domain") + .pathParam("service", "ServiceName", "name of the provider service") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("detail", "Tenancy", "tenancy object") + .auth("update", "{domain}:tenancy") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Tenancy", "GET", "/domain/{domain}/tenancy/{service}") + .comment("Retrieve the specified tenant.") + .pathParam("domain", "DomainName", "name of the tenant domain") + .pathParam("service", "ServiceName", "name of the provider service") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Tenancy", "DELETE", "/domain/{domain}/tenancy/{service}") + .comment("Delete the tenant from the specified service. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("domain", "DomainName", "name of the tenant domain") + .pathParam("service", "ServiceName", "name of the provider service") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("delete", "{domain}:tenancy") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TenancyResourceGroup", "PUT", "/domain/{domain}/tenancy/{service}/resourceGroup/{resourceGroup}") + .comment("Add a new resource group for the tenant for the specified service.") + .pathParam("domain", "DomainName", "name of the tenant domain") + .pathParam("service", "ServiceName", "name of the provider service") + .pathParam("resourceGroup", "EntityName", "tenant resource group") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("detail", "TenancyResourceGroup", "tenancy resource group object") + .auth("update", "{domain}:tenancy.{service}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TenancyResourceGroup", "DELETE", "/domain/{domain}/tenancy/{service}/resourceGroup/{resourceGroup}") + .comment("Delete the specified resource group for tenant from the specified service.") + .pathParam("domain", "DomainName", "name of the tenant domain") + .pathParam("service", "ServiceName", "name of the provider service") + .pathParam("resourceGroup", "EntityName", "tenant resource group") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("update", "{domain}:tenancy.{service}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TenantRoles", "PUT", "/domain/{domain}/service/{service}/tenant/{tenantDomain}") + .comment("Create/update set of roles for a given tenant.") + .pathParam("domain", "DomainName", "name of the provider domain") + .pathParam("service", "SimpleName", "name of the provider service") + .pathParam("tenantDomain", "DomainName", "name of the tenant domain") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("detail", "TenantRoles", "list of roles to be added/updated for the tenant") + .auth("update", "{domain}:tenant.{tenantDomain}") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TenantRoles", "GET", "/domain/{domain}/service/{service}/tenant/{tenantDomain}") + .comment("Retrieve the configured set of roles for the tenant.") + .pathParam("domain", "DomainName", "name of the provider domain") + .pathParam("service", "SimpleName", "name of the provider service") + .pathParam("tenantDomain", "DomainName", "name of the tenant domain") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TenantRoles", "DELETE", "/domain/{domain}/service/{service}/tenant/{tenantDomain}") + .comment("Delete the configured set of roles for the tenant. Upon successful completion of this delete request, the server will return NO_CONTENT status code without any data (no object will be returned).") + .pathParam("domain", "DomainName", "name of the provider domain") + .pathParam("service", "SimpleName", "name of the provider service") + .pathParam("tenantDomain", "DomainName", "name of the tenant domain") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("delete", "{domain}:tenant.{tenantDomain}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TenantResourceGroupRoles", "PUT", "/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + .comment("Create/update set of roles for a given tenant and resource group") + .pathParam("domain", "DomainName", "name of the provider domain") + .pathParam("service", "SimpleName", "name of the provider service") + .pathParam("tenantDomain", "DomainName", "name of the tenant domain") + .pathParam("resourceGroup", "EntityName", "tenant resource group") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("detail", "TenantResourceGroupRoles", "list of roles to be added/updated for the tenant") + .auth("update", "{domain}:tenant.{tenantDomain}") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TenantResourceGroupRoles", "GET", "/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + .comment("Retrieve the configured set of roles for the tenant and resource group") + .pathParam("domain", "DomainName", "name of the provider domain") + .pathParam("service", "SimpleName", "name of the provider service") + .pathParam("tenantDomain", "DomainName", "name of the tenant domain") + .pathParam("resourceGroup", "EntityName", "tenant resource group") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TenantResourceGroupRoles", "DELETE", "/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + .comment("Delete the configured set of roles for the tenant and resource group") + .pathParam("domain", "DomainName", "name of the provider domain") + .pathParam("service", "SimpleName", "name of the provider service") + .pathParam("tenantDomain", "DomainName", "name of the tenant domain") + .pathParam("resourceGroup", "EntityName", "tenant resource group") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("update", "{domain}:tenant.{tenantDomain}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("ProviderResourceGroupRoles", "PUT", "/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + .comment("Create/update set of roles for a given provider and resource group") + .pathParam("tenantDomain", "DomainName", "name of the tenant domain") + .pathParam("provDomain", "DomainName", "name of the provider domain") + .pathParam("provService", "SimpleName", "name of the provider service") + .pathParam("resourceGroup", "EntityName", "tenant resource group") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .input("detail", "ProviderResourceGroupRoles", "list of roles to be added/updated for the provider") + .auth("update", "{tenantDomain}:tenancy.{provDomain}.{provService}") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("ProviderResourceGroupRoles", "GET", "/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + .comment("Retrieve the configured set of roles for the provider and resource group") + .pathParam("tenantDomain", "DomainName", "name of the tenant domain") + .pathParam("provDomain", "DomainName", "name of the provider domain") + .pathParam("provService", "SimpleName", "name of the provider service") + .pathParam("resourceGroup", "EntityName", "tenant resource group") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("ProviderResourceGroupRoles", "DELETE", "/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + .comment("Delete the configured set of roles for the provider and resource group") + .pathParam("tenantDomain", "DomainName", "name of the tenant domain") + .pathParam("provDomain", "DomainName", "name of the provider domain") + .pathParam("provService", "SimpleName", "name of the provider service") + .pathParam("resourceGroup", "EntityName", "tenant resource group") + .headerParam("Y-Audit-Ref", "auditRef", "String", null, "Audit param required(not empty) if domain auditEnabled is true.") + .auth("update", "{tenantDomain}:tenancy.{provDomain}.{provService}") + .expected("NO_CONTENT") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("CONFLICT", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Access", "GET", "/access/{action}/{resource}") + .comment("Check access for the specified operation on the specified resource for the currently authenticated user. This is the slow centralized access for control-plane purposes. Use distributed mechanisms for decentralized (data-plane) access by fetching signed policies and role tokens for users.") + .pathParam("action", "ActionName", "action as specified in the policy assertion, i.e. update or read") + .pathParam("resource", "YRN", "the resource to check access against, i.e. \"media.news:articles\"") + .queryParam("domain", "domain", "DomainName", null, "usually null. If present, it specifies an alternate domain for cross-domain trust relation") + .queryParam("principal", "checkPrincipal", "EntityName", null, "usually null. If present, carry out the access check for this principal") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Access", "GET", "/access/{action}") + .name("GetAccessExt") + .pathParam("action", "ActionName", "action as specified in the policy assertion, i.e. update or read") + .queryParam("resource", "resource", "String", null, "the resource to check access against, i.e. \"media.news:articles\"") + .queryParam("domain", "domain", "DomainName", null, "usually null. If present, it specifies an alternate domain for cross-domain trust relation") + .queryParam("principal", "checkPrincipal", "EntityName", null, "usually null. If present, carry out the access check for this principal") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("ResourceAccessList", "GET", "/resource") + .comment("Return list of resources that the given principal has access to. Even though the principal is marked as optional, it must be specified unless the caller has authorization from sys.auth domain to check access for all user principals. (action: access, resource: resource-lookup-all)") + .queryParam("principal", "principal", "EntityName", null, "specifies principal to query the resource list for") + .queryParam("action", "action", "ActionName", null, "action as specified in the policy assertion") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("SignedDomains", "GET", "/sys/modified_domains") + .comment("Retrieve the list of modified domains since the specified timestamp. The server will return the list of all modified domains and the latest modification timestamp as the value of the ETag header. The client will need to use this value during its next call to request the changes since the previous request. When metaonly set to true, dont add roles, policies or services, dont sign") + .queryParam("domain", "domain", "DomainName", null, "filter the domain list only to the specified name") + .queryParam("metaonly", "metaOnly", "String", null, "valid values are \"true\" or \"false\"") + .headerParam("If-None-Match", "matchingTag", "String", null, "Retrieved from the previous request, this timestamp specifies to the server to return any domains modified since this time") + .output("ETag", "tag", "String", "") + .auth("", "", true) + .expected("OK") + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("UserToken", "GET", "/user/{userName}/token") + .comment("Return a user/principal token for the specified authenticated user. Typical authenticated users with their native credentials are not allowed to update their domain data. They must first obtain a UserToken and then use that token for authentication and authorization of their update requests.") + .pathParam("userName", "SimpleName", "name of the user") + .queryParam("services", "serviceNames", "String", null, "comma separated list of on-behalf-of service names") + .auth("", "", true) + .expected("OK") + .exception("FORBIDDEN", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("UserToken", "OPTIONS", "/user/{userName}/token") + .comment("CORS (Cross-Origin Resource Sharing) support to allow Provider Services to obtain AuthorizedService Tokens on behalf of Tenant administrators") + .pathParam("userName", "SimpleName", "name of the user") + .queryParam("services", "serviceNames", "String", null, "comma separated list of on-behalf-of service names") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") +; + + sb.resource("ServicePrincipal", "GET", "/principal") + .comment("Return a ServicePrincipal object if the serviceToken is valid. This request provides a simple operation that an external application can execute to validate a service token.") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("ServerTemplateList", "GET", "/template") + .comment("Get the list of solution templates defined in the server") + .auth("", "", true) + .expected("OK") + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Template", "GET", "/template/{template}") + .comment("Get solution template details. Includes the roles and policies that will be automatically provisioned when the template is applied to a domain") + .pathParam("template", "SimpleName", "name of the solution template") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + + return sb.build(); + } + +} diff --git a/core/zms/src/main/rdl/Access.rdli b/core/zms/src/main/rdl/Access.rdli new file mode 100644 index 00000000000..4604081262d --- /dev/null +++ b/core/zms/src/main/rdl/Access.rdli @@ -0,0 +1,70 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; +include "Policy.tdl"; + +//Access can be checked and returned as this resource. +type Access Struct { + Bool granted; //true (allowed) or false (denied) +} + +//Check access for the specified operation on the specified resource +//for the currently authenticated user. This is the slow centralized +//access for control-plane purposes. Use distributed mechanisms for +//decentralized (data-plane) access by fetching signed policies and +//role tokens for users. +resource Access GET "/access/{action}/{resource}?domain={domain}&principal={checkPrincipal}" { + ActionName action; //action as specified in the policy assertion, i.e. update or read + YRN resource; //the resource to check access against, i.e. "media.news:articles" + DomainName domain (optional); //usually null. If present, it specifies an alternate domain for cross-domain trust relation + EntityName checkPrincipal (optional); //usually null. If present, carry out the access check for this principal + authenticate; + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError NOT_FOUND; + } +} + +resource Access GET "/access/{action}?resource={resource}&domain={domain}&principal={checkPrincipal}" (name=GetAccessExt) { + ActionName action; //action as specified in the policy assertion, i.e. update or read + String resource; //the resource to check access against, i.e. "media.news:articles" + DomainName domain (optional); //usually null. If present, it specifies an alternate domain for cross-domain trust relation + EntityName checkPrincipal (optional); //usually null. If present, carry out the access check for this principal + authenticate; + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError NOT_FOUND; + } +} + +type ResourceAccess Struct { + EntityName principal; + Array assertions; +} + +type ResourceAccessList Struct { + Array resources; +} + +//Return list of resources that the given principal has access to. Even though the principal +//is marked as optional, it must be specified unless the caller has authorization from sys.auth +//domain to check access for all user principals. (action: access, resource: resource-lookup-all) +resource ResourceAccessList GET "/resource?principal={principal}&action={action}" { + EntityName principal (optional); //specifies principal to query the resource list for + ActionName action (optional); //action as specified in the policy assertion + authenticate; + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError NOT_FOUND; + } +} diff --git a/core/zms/src/main/rdl/Domain.rdli b/core/zms/src/main/rdl/Domain.rdli new file mode 100644 index 00000000000..b1b3381d067 --- /dev/null +++ b/core/zms/src/main/rdl/Domain.rdli @@ -0,0 +1,233 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +//Domain API +include "Names.tdl"; +include "Domain.tdl"; +include "Template.tdl"; + +//Get info for the specified domain, by name. This request only returns +//the configured domain attributes and not any domain objects like roles, +//policies or service identities. +resource Domain GET "/domain/{domain}" { + DomainName domain; //name of the domain + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//A paginated list of domains. +type DomainList Struct { + Array names; //list of domain names + String next (optional); //if the response is a paginated list, this attribute specifies the value to be used in the next domain list request as the value for the skip query parameter. +} + +//Enumerate domains. Can be filtered by prefix and depth, and paginated. +//This operation can be expensive, as it may span multiple domains. +resource DomainList GET "/domain?limit={limit}&skip={skip}&prefix={prefix}&depth={depth}&account={account}&ypmid={productId}&member={roleMember}&role={roleName}" { + Int32 limit (optional); //restrict the number of results in this call + String skip (optional); //restrict the set to those after the specified "next" token returned from a previous call + String prefix (optional); //restrict to names that start with the prefix + Int32 depth (optional); //restrict the depth of the name, specifying the number of '.' characters that can appear + String account (optional); //restrict to domain names that have specified account name + Int32 productId (optional); //restrict the domain names that have specified product id + ResourceName roleMember (optional); //restrict the domain names where the specified user is in a role - see roleName + ResourceName roleName (optional); //restrict the domain names where the specified user is in this role - see roleMember + String modifiedSince (header="If-Modified-Since"); //This header specifies to the server to return any domains modified since this HTTP date + exceptions { + ResourceError UNAUTHORIZED; + } +} + +//Set of metadata attributes that all domains may have and can be changed. +type DomainMeta Struct { + String description (optional); //a description of the domain + ResourceName org (optional); //a reference to an Organization. (i.e. org:media) + Bool enabled (optional, default=true); //Future use only, currently not used + Bool auditEnabled (optional, default=false); //Flag indicates whether or not domain modifications should be logged for SOX+Auditing. If true, the auditRef parameter must be supplied(not empty) for any API defining it. + String account (optional); // associated cloud (i.e. aws) account id + Int32 ypmId (optional); // associated product id +} + +//Top Level Domain object. The required attributes include the name of the +//domain and list of domain administrators. +type TopLevelDomain DomainMeta { + SimpleName name; //name of the domain + Array adminUsers; //list of domain administrators + DomainTemplateList templates (optional); //list of solution template names +} + +//A Subdomain is a TopLevelDomain, except it has a parent. +type SubDomain TopLevelDomain { + DomainName parent; //name of the parent domain +} + +//Create a new top level domain. This is a privileged action for the "sys.auth" administrators. +resource Domain POST "/domain" { + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + TopLevelDomain detail; //TopLevelDomain object to be created + authorize ("create", "sys.auth:domain"); + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Create a new subdomain. The domain administrators of the {parent} domain +//have the privilege to create subdomains. +resource Domain POST "/subdomain/{parent}" { + DomainName parent; //name of the parent domain + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + SubDomain detail; //Subdomain object to be created + authorize ("create", "{parent}:domain"); + expected OK; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//A UserDomain is the user's own top level domain in user - e.g. user.hga +type UserDomain DomainMeta { + SimpleName name; //user id which will be the domain name + DomainTemplateList templates (optional); //list of solution template names +} + +//Create a new user domain. The user domain will be created in the user +//top level domain and the user himself will be set as the administrator +//for this domain. +resource Domain POST "/userdomain/{name}" { + SimpleName name; //name of the domain which will be the user id + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + UserDomain detail; //UserDomain object to be created + authorize ("create", "user.{name}:domain"); + expected OK; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Delete the specified domain. This is a privileged action for the "sys.auth" administrators. +//Upon successful completion of this delete request, the server will return NO_CONTENT status +//code without any data (no object will be returned). +resource TopLevelDomain DELETE "/domain/{name}" { + DomainName name; //name of the domain to be deleted + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("delete", "sys.auth:domain"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Delete the specified subdomain. Caller must have domain delete permissions in parent. +//Upon successful completion of this delete request, the server will return NO_CONTENT status +//code without any data (no object will be returned). +resource SubDomain DELETE "/subdomain/{parent}/{name}" { + DomainName parent; //name of the parent domain + DomainName name; //name of the subdomain to be deleted + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("delete", "{parent}:domain"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Delete the specified userdomain. Caller must have domain delete permissions in the domain. +//Upon successful completion of this delete request, the server will return NO_CONTENT status +//code without any data (no object will be returned). +resource UserDomain DELETE "/userdomain/{name}" { + SimpleName name; //name of the domain to be deleted which will be the user id + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("delete", "user.{name}:domain"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Update the specified top level domain metadata. Note that entities in the domain +//are not affected. Caller must have update privileges on the domain itself. +resource Domain PUT "/domain/{name}/meta" { + DomainName name; //name of the domain to be updated + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + DomainMeta detail; //DomainMeta object with updated attribute values + authorize ("update", "{name}:"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Update the given domain by applying the roles and policies defined +//in the specified solution template(s). Caller must have UPDATE privileges +//on the domain itself. +resource DomainTemplate PUT "/domain/{name}/template" { + DomainName name; //name of the domain to be updated + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + DomainTemplate template; //DomainTemplate object with solution template name(s) + authorize ("update", "{name}:"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Get the list of solution templates applied to a domain +resource DomainTemplateList GET "/domain/{name}/template" { + DomainName name; //name of the domain + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} + +//Update the given domain by deleting the specified template from the +//domain template list. Cycles through the roles and policies defined in the +//template and deletes them. +//Caller must have delete privileges on the domain itself. +resource DomainTemplate DELETE "/domain/{name}/template/{template}" { + DomainName name; //name of the domain to be updated + SimpleName template; //name of the solution template + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("delete", "{name}:"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} diff --git a/core/zms/src/main/rdl/Domain.tdl b/core/zms/src/main/rdl/Domain.tdl new file mode 100644 index 00000000000..2adfb67cee0 --- /dev/null +++ b/core/zms/src/main/rdl/Domain.tdl @@ -0,0 +1,23 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +//Domain types +include "Names.tdl"; + +//A domain is an independent partition of users, roles, and resources. +//Its name represents the definition of a namespace; the only way a +//new namespace can be created, from the top, is by creating Domains. +//Administration of a domain is governed by the parent domain (using +//reverse-DNS namespaces). The top level domains are governed by the +//special "sys.auth" domain. +type Domain Struct { + DomainName name; //the common name to be referred to, the symbolic id. It is immutable + Timestamp modified (optional); //the last modification timestamp of any object or attribute in this domain + UUID id (optional); //unique identifier of the domain. generated on create, never reused + String description (optional); //description of the domain + ResourceName org (optional); //a reference to an Organization + Bool enabled (optional, default=true); //Future use only, currently not used + Bool auditEnabled (optional, default=false); //Flag indicates whether or not domain modifications should be logged for SOX+Auditing. If true, the auditRef parameter must be supplied(not empty) for any API defining it. + String account (optional); // associated cloud (i.e. aws) account id + Int32 ypmId (optional); // associated product id +} diff --git a/core/zms/src/main/rdl/DomainDataCheck.rdli b/core/zms/src/main/rdl/DomainDataCheck.rdli new file mode 100644 index 00000000000..961475a7765 --- /dev/null +++ b/core/zms/src/main/rdl/DomainDataCheck.rdli @@ -0,0 +1,52 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +// DomainDataCheck API + +include "Names.tdl"; + +// A dangling policy where the assertion is referencing +// a role name that doesn't exist in the domain +type DanglingPolicy Struct { + EntityName policyName; + EntityName roleName; +} + +// Domain data object representing the results of a +// check operation looking for dangling roles, policies +// and trust relationships that are set either on tenant +// or provider side only +type DomainDataCheck Struct { + + // Names of roles not specified in any assertion. + // Might be empty or null if no dangling roles. + Array danglingRoles (optional); + + // Policy+role tuples where role doesnt exist. + // Might be empty or null if no dangling policies. + Array danglingPolicies (optional); + + Int32 policyCount; // total number of policies + Int32 assertionCount; // total number of assertions + Int32 roleWildCardCount; // total number of assertions containing roles as wildcards + + // Service names (domain.service) that dont contain trust role if this is a tenant domain. + // Might be empty or null, if not a tenant or if all providers support this tenant. + Array providersWithoutTrust (optional); + + // Names of Tenant domains that dont contain assume role assertions if this is a provider domain. + // Might be empty or null, if not a provider or if all tenants support use this provider. + Array tenantsWithoutAssumeRole (optional); +} + +// Carry out data check operation for the specified domain. +resource DomainDataCheck GET "/domain/{domainName}/check" { + DomainName domainName; //name of the domain + authenticate; + exceptions { + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + diff --git a/core/zms/src/main/rdl/Entity.rdli b/core/zms/src/main/rdl/Entity.rdli new file mode 100644 index 00000000000..978b0dba489 --- /dev/null +++ b/core/zms/src/main/rdl/Entity.rdli @@ -0,0 +1,66 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +//Entity API. Not strictly needed for Athenz, but useful for storing info in/about domains. +include "Names.tdl"; +include "Entity.tdl"; + +//Put an entity into the domain. +resource Entity PUT "/domain/{domainName}/entity/{entityName}" { + DomainName domainName; //name of the domain + EntityName entityName; //name of entity + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + Entity entity; //Entity object to be added to the domain + authorize ("update", "{domainName}:{entityName}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Get a entity from a domain. +resource Entity GET "/domain/{domainName}/entity/{entityName}" { + DomainName domainName; //name of the domain + EntityName entityName; //name of entity + authenticate; //open for all authenticated users to read + expected OK; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Delete the entity from the domain. Upon successful completion of this delete +//request, the server will return NO_CONTENT status code without any data +//(no object will be returned). +resource Entity DELETE "/domain/{domainName}/entity/{entityName}" { + DomainName domainName; //name of the domain + EntityName entityName; //name of entity + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("delete", "{domainName}:{entityName}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Enumerate entities provisioned in this domain. +resource EntityList GET "/domain/{domainName}/entity" { + DomainName domainName; //name of the domain + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} diff --git a/core/zms/src/main/rdl/Entity.tdl b/core/zms/src/main/rdl/Entity.tdl new file mode 100644 index 00000000000..f2158c81022 --- /dev/null +++ b/core/zms/src/main/rdl/Entity.tdl @@ -0,0 +1,17 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +//Entity API. Not strictly needed for Athenz, but useful for storing info in/about domains. +include "Names.tdl"; + +//An entity is a name and a structured value. +//some entity names/prefixes are reserved (i.e. "role", "policy", "meta", "domain", "service") +type Entity Struct { + EntityName name; //name of the entity object + Struct value; //value of the entity +} + +//The representation for an enumeration of entities in the namespace +type EntityList Struct { + Array names; //list of entity names +} diff --git a/core/zms/src/main/rdl/Names.tdl b/core/zms/src/main/rdl/Names.tdl new file mode 100644 index 00000000000..561c975a51c --- /dev/null +++ b/core/zms/src/main/rdl/Names.tdl @@ -0,0 +1,58 @@ +// +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. +// +//Common name types used by several API definitions +// + +//A simple identifier, an element of compound name. +type SimpleName String (pattern="[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + +//A compound name. Most names in this API are compound names. +type CompoundName String (pattern="({SimpleName}\\.)*{SimpleName}"); + +//A domain name is the general qualifier prefix, as its uniqueness is managed. +type DomainName String (pattern="{CompoundName}"); + +//An entity name is a short form of a resource name, +//including only the domain and entity. +type EntityName String (pattern="{CompoundName}"); + +//A service name will generally be a unique subdomain. +type ServiceName String (pattern="{CompoundName}"); + +//A location name is not yet defined, but will be a +//dotted name like everything else. +type LocationName String (pattern="{CompoundName}"); + +//An action (operation) name. +type ActionName String (pattern="{CompoundName}"); + +//A shorthand for a YRN with no service or location. +//The 'tail' of a YRN, just the domain:entity. +//Note that the EntityName part is optional, that is, +//a domain name followed by a colon is valid resource name. +type ResourceName String (pattern="{DomainName}(:{EntityName})?"); + +//A full Yahoo Resource name (YRN). +type YRN String (pattern="(yrn:({ServiceName})?:({LocationName})?:)?{ResourceName}"); + +//The Y-specific URL-safe Base64 variant. +type YBase64 String (pattern="[a-zA-Z0-9\\._-]+"); + +//YEncoded includes ybase64 chars, as well as = and %. +//This can represent a user cookie and URL-encoded values. +type YEncoded String (pattern="[a-zA-Z0-9\\._%=-]*"); + +//Used as the prefix in a signed assertion. This uniquely +//identifies a signing authority. +type AuthorityName String (pattern="{CompoundName}"); //i.e. "user" + +//A signed assertion if identity. i.e. the user cookie value. +//This token will only make sense to the authority that +//generated it, so it is beneficial to have something in the +//value that is cheaply recognized to quickly reject if +//it belongs to another authority. In addition to the +//YEncoded set our token includes ; to separate components +//and , to separate roles and : for IPv6 addresses +type SignedToken String (pattern="[a-zA-Z0-9\\._%=:;,-]*"); diff --git a/core/zms/src/main/rdl/Policy.rdli b/core/zms/src/main/rdl/Policy.rdli new file mode 100644 index 00000000000..17c20bce7bd --- /dev/null +++ b/core/zms/src/main/rdl/Policy.rdli @@ -0,0 +1,136 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +//Policy API +include "Names.tdl"; +include "Policy.tdl"; + +//The representation for an enumeration of policies in the namespace, with pagination. +type PolicyList Struct { + Array names; //list of policy names + String next (optional); //if the response is a paginated list, this attribute specifies the value to be used in the next policy list request as the value for the skip query parameter. +} + +//List policies provisioned in this namespace. +resource PolicyList GET "/domain/{domainName}/policy?limit={limit}&skip={skip}" { + DomainName domainName; //name of the domain + Int32 limit (optional); //restrict the number of results in this call + String skip (optional); //restrict the set to those after the specified "next" token returned from a previous call + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//List policies provisioned in this namespace. +resource Policies GET "/domain/{domainName}/policies?assertions={assertions}" { + DomainName domainName; //name of the domain + Bool assertions (optional, default=false); // return list of assertions in the policy + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} + +//Read the specified policy. +resource Policy GET "/domain/{domainName}/policy/{policyName}" { + DomainName domainName; //name of the domain + EntityName policyName; //name of the policy to be retrieved + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Create or update the specified policy. +resource Policy PUT "/domain/{domainName}/policy/{policyName}" { + DomainName domainName; //name of the domain + EntityName policyName; //name of the policy to be added/updated + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + Policy policy; //Policy object to be added or updated in the domain + authorize("update", "{domainName}:policy.{policyName}"); + expected NO_CONTENT; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Delete the specified policy. Upon successful completion of this delete +//request, the server will return NO_CONTENT status code without any data +//(no object will be returned). +resource Policy DELETE "/domain/{domainName}/policy/{policyName}" { + DomainName domainName; //name of the domain + EntityName policyName; //name of the policy to be deleted + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize("delete", "{domainName}:policy.{policyName}"); + expected NO_CONTENT; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Get the assertion details with specified id in the given policy +resource Assertion GET "/domain/{domainName}/policy/{policyName}/assertion/{assertionId}" { + DomainName domainName; //name of the domain + EntityName policyName; //name of the policy + Int64 assertionId; //assertion id + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Add the specified assertion to the given policy +resource Assertion PUT "/domain/{domainName}/policy/{policyName}/assertion" { + DomainName domainName; //name of the domain + EntityName policyName; //name of the policy + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + Assertion assertion; //Assertion object to be added to the given policy + authorize ("update", "{domainName}:policy.{policyName}"); + expected OK, CREATED; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Delete the specified policy assertion. Upon successful completion of this delete +//request, the server will return NO_CONTENT status code without any data (no +//object will be returned). +resource Assertion DELETE "/domain/{domainName}/policy/{policyName}/assertion/{assertionId}" { + DomainName domainName; //name of the domain + EntityName policyName; //name of the policy + Int64 assertionId; //assertion id + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("update", "{domainName}:policy.{policyName}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} \ No newline at end of file diff --git a/core/zms/src/main/rdl/Policy.tdl b/core/zms/src/main/rdl/Policy.tdl new file mode 100644 index 00000000000..ac6d1d2d2ab --- /dev/null +++ b/core/zms/src/main/rdl/Policy.tdl @@ -0,0 +1,30 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +//Policy Types +include "Names.tdl"; + +//Every assertion can have the effect of ALLOW or DENY. +type AssertionEffect Enum { ALLOW, DENY } + +//A representation for the encapsulation of an action to be performed on a +//resource by a principal. +type Assertion Struct { + String role; //the subject of the assertion - a role + String resource; //the object of the assertion. Must be in the local namespace. Can contain wildcards + String action; //the predicate of the assertion. Can contain wildcards + AssertionEffect effect (optional, default=ALLOW); //the effect of the assertion in the policy language + Int64 id (optional); //assertion id - auto generated by server. Not required during put operations. +} + +//The representation for a Policy with set of assertions. +type Policy Struct { + ResourceName name; //name of the policy + Timestamp modified (optional); //last modification timestamp of this policy + Array assertions; //list of defined assertions for this policy +} + +//The representation of list of policy objects +type Policies Struct { + Array list; // list of policy objects +} diff --git a/core/zms/src/main/rdl/Provider.rdl b/core/zms/src/main/rdl/Provider.rdl new file mode 100644 index 00000000000..b6685689ce9 --- /dev/null +++ b/core/zms/src/main/rdl/Provider.rdl @@ -0,0 +1,12 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +// +//This "service" definition is just to provide a generic client +//stub for any service implementing the provider API +// +name Provider; +version 1; +namespace com.yahoo.athenz.provider; + +include "Provider.rdli"; diff --git a/core/zms/src/main/rdl/Provider.rdli b/core/zms/src/main/rdl/Provider.rdli new file mode 100644 index 00000000000..777a897c165 --- /dev/null +++ b/core/zms/src/main/rdl/Provider.rdli @@ -0,0 +1,109 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +// +//The Provider API is the API to be implement to host tenants +// +include "Names.tdl"; + +type TenantState Enum { INACTIVE, PENDING, ACTIVE } + +type Tenant Struct { + SimpleName service; //name of the service + DomainName name; //name of the tenant domain in this service. Must be a valid domain name the caller has rights to + TenantState state (optional, default=ACTIVE); //the state of the tenant + Array roles (optional); //the roles this tenant may assume. Determined by and returned by this service + Array resourceGroups (optional); //registered resource groups for this tenant +} + +type TenantResourceGroup Struct { + SimpleName service; //name of the service + DomainName name; //name of the tenant domain in this service. Must be a valid domain name the caller has rights to + EntityName resourceGroup; //resource group for this tenant + Array roles (optional); //the roles this tenant may assume. Determined by and returned by this service +} + +//Create a new tenant in this provider +resource Tenant PUT "/service/{service}/tenant/{tenant}" { + SimpleName service; //name of the service + DomainName tenant; //name of the tenant domain in this service + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + Tenant template; + authorize("assume_role", "role.{service}.tenant.{tenant}.admin", "{tenant}"); //note optional 3rd arg in this case -- it is unusual! + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + Tenant ACCEPTED; + } +} + +//Get information about the tenant +resource Tenant GET "/service/{service}/tenant/{tenant}" { + SimpleName service; //name of the service + DomainName tenant; //name of the tenant domain in this service + authenticate; + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Remove a tenant and all its resources. Upon successful completion of this delete +//request, the server will return NO_CONTENT status code without any data (no +//object will be returned). +resource Tenant DELETE "/service/{service}/tenant/{tenant}" { + SimpleName service; //name of the service + DomainName tenant; //name of the tenant domain in this service + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize("assume_role", "role.{service}.tenant.{tenant}.admin", "{tenant}"); + expected NO_CONTENT; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Create a new resource group for this tenant in this provider. The tenant +//must already be registered with the provider +resource TenantResourceGroup PUT "/service/{service}/tenant/{tenant}/resourceGroup/{resourceGroup}" { + SimpleName service; //name of the service + DomainName tenant; //name of the tenant domain in this service + EntityName resourceGroup; //tenant resource group + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + TenantResourceGroup template; + authorize("assume_role", "role.{service}.tenant.{tenant}.admin", "{tenant}"); + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + Tenant ACCEPTED; + } +} + +//Remove the specified resource group from this tenant and all its resources. +resource TenantResourceGroup DELETE "/service/{service}/tenant/{tenant}/resourceGroup/{resourceGroup}" { + SimpleName service; //name of the service + DomainName tenant; //name of the tenant domain in this service + EntityName resourceGroup; //tenant resource group + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize("assume_role", "role.{service}.tenant.{tenant}.admin", "{tenant}"); + expected NO_CONTENT; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} diff --git a/core/zms/src/main/rdl/Role.rdli b/core/zms/src/main/rdl/Role.rdli new file mode 100644 index 00000000000..b2fd46fe833 --- /dev/null +++ b/core/zms/src/main/rdl/Role.rdli @@ -0,0 +1,151 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +//Role API +include "Role.tdl"; + +//Enumerate roles provisioned in this domain. +resource RoleList GET "/domain/{domainName}/role?limit={limit}&skip={skip}" { + DomainName domainName; //name of the domain + Int32 limit (optional); //restrict the number of results in this call + String skip (optional); //restrict the set to those after the specified "next" token returned from a previous call + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +// Get the list of all roles in a domain with optional flag +// whether or not include members +resource Roles GET "/domain/{domainName}/roles?members={members}" { + DomainName domainName; // name of the domain + Bool members (optional, default=false); // return list of members in the role + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} + +//Get the specified role in the domain. +resource Role GET "/domain/{domainName}/role/{roleName}?auditLog={auditLog}&expand={expand}" { + DomainName domainName; //name of the domain + EntityName roleName; //name of the role to be retrieved + Bool auditLog (optional, default=false); //flag to indicate whether or not to return role audit log + Bool expand (optional, default=false); // expand delegated trust roles and return trusted members + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Create/update the specified role. +resource Role PUT "/domain/{domainName}/role/{roleName}" { + DomainName domainName; //name of the domain + EntityName roleName; //name of the role to be added/updated + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + Role role; //Role object to be added/updated in the domain + authorize ("update", "{domainName}:role.{roleName}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Delete the specified role. Upon successful completion of this delete +//request, the server will return NO_CONTENT status code without any +//data (no object will be returned). +resource Role DELETE "/domain/{domainName}/role/{roleName}" { + DomainName domainName; //name of the domain + EntityName roleName; //name of the role to be deleted + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("delete", "{domainName}:role.{roleName}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Get the membership status for a specified user in a role. +resource Membership GET "/domain/{domainName}/role/{roleName}/member/{memberName}" { + DomainName domainName; //name of the domain + EntityName roleName; //name of the role + ResourceName memberName; //user name to be checked for membership + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Add the specified user to the role's member list. +resource Membership PUT "/domain/{domainName}/role/{roleName}/member/{memberName}" { + DomainName domainName; //name of the domain + EntityName roleName; //name of the role + ResourceName memberName; //name of the user to be added as a member + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + Membership membership; //Membership object (must contain role/member names as specified in the URI) + authorize ("update", "{domainName}:role.{roleName}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Delete the specified role membership. Upon successful completion of this delete +//request, the server will return NO_CONTENT status code without any data (no +//object will be returned). +resource Membership DELETE "/domain/{domainName}/role/{roleName}/member/{memberName}" { + DomainName domainName; //name of the domain + EntityName roleName; //name of the role + ResourceName memberName; //name of the user to be removed as a member + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("update", "{domainName}:role.{roleName}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Verify and, if necessary, fix domain roles and policies to make sure the given +//set of users have administrative access to the domain. This request is only +//restricted to "sys.auth" domain administrators and can be used when the domain +//administrators incorrectly have blocked their own access to their domains. +resource DefaultAdmins PUT "/domain/{domainName}/admins" { + DomainName domainName; //name of the domain + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + DefaultAdmins defaultAdmins; //list of domain administrators + authorize ("update", "sys.auth:domain"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} diff --git a/core/zms/src/main/rdl/Role.tdl b/core/zms/src/main/rdl/Role.tdl new file mode 100644 index 00000000000..0b69f6ebd15 --- /dev/null +++ b/core/zms/src/main/rdl/Role.tdl @@ -0,0 +1,45 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; + +//The representation for an enumeration of roles in the namespace, with pagination. +type RoleList Struct { + Array names; //list of role names + String next (optional); //if the response is a paginated list, this attribute specifies the value to be used in the next role list request as the value for the skip query parameter. +} + +//An audit log entry for role membership change. +type RoleAuditLog Struct { + ResourceName member; //name of the role member + ResourceName admin; //name of the principal executing the change + Timestamp created; //timestamp of the entry + String action; //log action - either add or delete + String auditRef (optional); //audit reference string for the change as supplied by admin +} + +//The representation for a Role with set of members. +type Role Struct { + ResourceName name; //name of the role + Timestamp modified (optional); //last modification timestamp of the role + Array members (optional); //an explicit list of members. Might be empty or null, if trust is set + DomainName trust (optional); //a trusted domain to delegate membership decisions to + Array auditLog (optional); //an audit log for role membership changes +} + +//The representation for a list of roles with full details +type Roles Struct { + Array list; // list of role objects +} + +//The representation for a role membership. +type Membership Struct { + ResourceName memberName; //name of the member + Bool isMember (optional, default=true); //flag to indicate whether or the user is a member or not + ResourceName roleName (optional); //name of the role +} + +//The list of domain administrators. +type DefaultAdmins Struct { + Array admins; //list of domain administrators +} diff --git a/core/zms/src/main/rdl/ServiceIdentity.rdli b/core/zms/src/main/rdl/ServiceIdentity.rdli new file mode 100644 index 00000000000..e42b77b6168 --- /dev/null +++ b/core/zms/src/main/rdl/ServiceIdentity.rdli @@ -0,0 +1,130 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "ServiceIdentity.tdl"; + +//Register the specified ServiceIdentity in the specified domain +resource ServiceIdentity PUT "/domain/{domain}/service/{service}" { + DomainName domain; //name of the domain + SimpleName service; //name of the service + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + ServiceIdentity detail; //ServiceIdentity object to be added/updated in the domain + authorize ("update", "{domain}:service"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Get info for the specified ServiceIdentity. +resource ServiceIdentity GET "/domain/{domain}/service/{service}" { + DomainName domain; //name of the domain + SimpleName service; //name of the service to be retrieved + authenticate; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Delete the specified ServiceIdentity. Upon successful completion of this +//delete request, the server will return NO_CONTENT status code without any +//data (no object will be returned). +resource ServiceIdentity DELETE "/domain/{domain}/service/{service}" { + DomainName domain; //name of the domain + SimpleName service; //name of the service to be deleted + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("delete", "{domain}:service"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Retrieve list of service identities +resource ServiceIdentities GET "/domain/{domainName}/services?publickeys={publickeys}&hosts={hosts}" { + DomainName domainName; //name of the domain + Bool publickeys (optional, default=false); // return list of public keys in the service + Bool hosts (optional, default=false); // return list of hosts in the service + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} + +//Enumerate services provisioned in this domain. +resource ServiceIdentityList GET "/domain/{domainName}/service?limit={limit}&skip={skip}" { + DomainName domainName;//name of the domain + Int32 limit (optional); //restrict the number of results in this call + String skip (optional); //restrict the set to those after the specified "next" token returned from a previous call + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Retrieve the specified public key from the service. +resource PublicKeyEntry GET "/domain/{domain}/service/{service}/publickey/{id}" { + DomainName domain; //name of the domain + SimpleName service; //name of the service + String id; //the identifier of the public key to be retrieved + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Add the specified public key to the service. +resource PublicKeyEntry PUT "/domain/{domain}/service/{service}/publickey/{id}" { + DomainName domain; //name of the domain + SimpleName service; //name of the service + String id; //the identifier of the public key to be added + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + PublicKeyEntry publicKeyEntry; //PublicKeyEntry object to be added/updated in the service + authorize ("update", "{domain}:service.{service}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Remove the specified public key from the service. Upon successful completion of +//this delete request, the server will return NO_CONTENT status code without any +//data (no object will be returned). +resource PublicKeyEntry DELETE "/domain/{domain}/service/{service}/publickey/{id}" { + DomainName domain; //name of the domain + SimpleName service; //name of the service + String id; //the identifier of the public key to be deleted + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("update", "{domain}:service.{service}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} diff --git a/core/zms/src/main/rdl/ServiceIdentity.tdl b/core/zms/src/main/rdl/ServiceIdentity.tdl new file mode 100644 index 00000000000..4ff1a9d3733 --- /dev/null +++ b/core/zms/src/main/rdl/ServiceIdentity.tdl @@ -0,0 +1,33 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; + +//The representation of the public key in a service identity object. +type PublicKeyEntry Struct { + String key; //the public key for the service + String id; //the key identifier (version or zone name) +} + +//The representation of the service identity object. +type ServiceIdentity Struct { + ServiceName name; //the full name of the service, i.e. "sports.storage" + Array publicKeys (optional); //array of public keys for key rotation + String providerEndpoint (optional); //if present, then this service can provision tenants via this endpoint. + Timestamp modified (optional); //the timestamp when this entry was last modified + String executable (optional); //the path of the executable that runs the service + Array hosts (optional); //list of host names that this service can run on + String user (optional); //local (unix) user name this service can run as + String group (optional); //local (unix) group name this service can run as +} + +//The representation of list of services +type ServiceIdentities Struct { + Array list; //list of services +} + +//The representation for an enumeration of services in the namespace, with pagination. +type ServiceIdentityList Struct { + Array names; //list of service names + String next (optional); //if the response is a paginated list, this attribute specifies the value to be used in the next service list request as the value for the skip query parameter. +} diff --git a/core/zms/src/main/rdl/SignedDomains.rdli b/core/zms/src/main/rdl/SignedDomains.rdli new file mode 100644 index 00000000000..95e5322139a --- /dev/null +++ b/core/zms/src/main/rdl/SignedDomains.rdli @@ -0,0 +1,79 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Policy.tdl"; +include "Role.tdl"; +include "ServiceIdentity.tdl"; +include "Entity.tdl"; + +//Tuple of domain-name and modification time-stamps. This object is returned +//when the caller has requested list of domains modified since a specific timestamp. +type DomainModified Struct { + DomainName name; //name of the domain + Int64 modified; //last modified timestamp of the domain +} + +//A list of {domain, modified-timestamp} tuples. +type DomainModifiedList Struct { + Array nameModList; //list of modified domains +} + +//We need to include the name of the domain in this struct since +//this data will be passed back to ZPU through ZTS so we need to +//sign not only the list of policies but also the corresponding +//domain name that the policies belong to. +type DomainPolicies Struct { + DomainName domain; //name of the domain + Array policies; //list of policies defined in this server +} + +//A signed bulk transfer of policies. The data is signed with server's +//private key. +type SignedPolicies Struct { + DomainPolicies contents; //list of policies defined in a domain + String signature; //signature generated based on the domain policies object + String keyId; //the identifier of the key used to generate the signature +} + +//A domain object that includes its roles, policies and services. +type DomainData Struct { + DomainName name; //name of the domain + String account (optional); // associated cloud (i.e. aws) account id + Int32 ypmId (optional); // associated product id + Array roles; //list of roles in the domain + SignedPolicies policies; //list of policies in the domain signed with ZMS private key + Array services; //list of services in the domain + Array entities; //list of entities in the domain + Timestamp modified; //last modification timestamp +} + +//A domain object signed with server's private key +type SignedDomain Struct { + DomainData domain; //domain object with its roles, policies and services + String signature; //signature generated based on the domain object + String keyId; //the identifier of the key used to generate the signature +} + +//A list of signed domain objects +type SignedDomains Struct { + Array domains; +} + +//Retrieve the list of modified domains since the specified timestamp. The +//server will return the list of all modified domains and the latest modification +//timestamp as the value of the ETag header. The client will need to use this +//value during its next call to request the changes since the previous request. +// When metaonly set to true, dont add roles, policies or services, dont sign +resource SignedDomains GET "/sys/modified_domains?domain={domain}&metaonly={metaOnly}" { + DomainName domain (optional); //filter the domain list only to the specified name + String metaOnly (optional); // valid values are "true" or "false" + String matchingTag (header="If-None-Match"); //Retrieved from the previous request, this timestamp specifies to the server to return any domains modified since this time + String tag (header="ETag", out); //The current latest modification timestamp is returned in this header + authenticate; + expected OK, NOT_MODIFIED; + exceptions { + ResourceError NOT_FOUND; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} diff --git a/core/zms/src/main/rdl/Template.rdli b/core/zms/src/main/rdl/Template.rdli new file mode 100644 index 00000000000..8f85b3f19d1 --- /dev/null +++ b/core/zms/src/main/rdl/Template.rdli @@ -0,0 +1,26 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; +include "Template.tdl"; + +//Get the list of solution templates defined in the server +resource ServerTemplateList GET "/template" { + authenticate; + exceptions { + ResourceError UNAUTHORIZED; + } +} + +//Get solution template details. Includes the roles and policies +//that will be automatically provisioned when the template +//is applied to a domain +resource Template GET "/template/{template}" { + SimpleName template; //name of the solution template + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} diff --git a/core/zms/src/main/rdl/Template.tdl b/core/zms/src/main/rdl/Template.tdl new file mode 100644 index 00000000000..9fa343c0433 --- /dev/null +++ b/core/zms/src/main/rdl/Template.tdl @@ -0,0 +1,31 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +//Template API +include "Names.tdl"; +include "Role.tdl"; +include "Policy.tdl"; + +//Solution Template object defined on the server +type Template Struct { + Array roles; //list of roles in the template + Array policies; //list of policies defined in this template +} + +//List of template names that is the base struct for +//server and domain templates +type TemplateList Struct { + Array templateNames; //list of template names +} + +//solution template(s) to be applied to a domain +type DomainTemplate TemplateList { +} + +//List of solution templates to be applied to a domain +type DomainTemplateList TemplateList { +} + +//List of solution templates available in the server +type ServerTemplateList TemplateList { +} diff --git a/core/zms/src/main/rdl/Tenancy.rdli b/core/zms/src/main/rdl/Tenancy.rdli new file mode 100644 index 00000000000..8934b455fa3 --- /dev/null +++ b/core/zms/src/main/rdl/Tenancy.rdli @@ -0,0 +1,101 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +//The API to create/remove a tenancy for a client domain. Called as that tenant's admins. +include "Names.tdl"; + +//A representation of tenant. +type Tenancy Struct { + DomainName domain; //the domain that is to get a tenancy + ServiceName service; //the provider service on which the tenancy is to reside + Array resourceGroups (optional); //registered resource groups for this tenant +} + +type TenancyResourceGroup Struct { + DomainName domain; //the domain that is to get a tenancy + ServiceName service; //the provider service on which the tenancy is to reside + EntityName resourceGroup; //registered resource group for this tenant +} + +//Add a tenant for the specified service. +resource Tenancy PUT "/domain/{domain}/tenancy/{service}" { + DomainName domain; //name of the tenant domain + ServiceName service; //name of the provider service + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + Tenancy detail; //tenancy object + authorize ("update", "{domain}:tenancy"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Retrieve the specified tenant. +resource Tenancy GET "/domain/{domain}/tenancy/{service}" { + DomainName domain; //name of the tenant domain + ServiceName service; //name of the provider service + authenticate; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Delete the tenant from the specified service. Upon successful completion of +//this delete request, the server will return NO_CONTENT status code without +//any data (no object will be returned). +resource Tenancy DELETE "/domain/{domain}/tenancy/{service}" { + DomainName domain; //name of the tenant domain + ServiceName service; //name of the provider service + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("delete", "{domain}:tenancy"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Add a new resource group for the tenant for the specified service. +resource TenancyResourceGroup PUT "/domain/{domain}/tenancy/{service}/resourceGroup/{resourceGroup}" { + DomainName domain; //name of the tenant domain + ServiceName service; //name of the provider service + EntityName resourceGroup; //tenant resource group + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + TenancyResourceGroup detail; //tenancy resource group object + authorize ("update", "{domain}:tenancy.{service}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Delete the specified resource group for tenant from the specified service. +resource TenancyResourceGroup DELETE "/domain/{domain}/tenancy/{service}/resourceGroup/{resourceGroup}" { + DomainName domain; //name of the tenant domain + ServiceName service; //name of the provider service + EntityName resourceGroup; //tenant resource group + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("update", "{domain}:tenancy.{service}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} diff --git a/core/zms/src/main/rdl/TenantRoles.rdli b/core/zms/src/main/rdl/TenantRoles.rdli new file mode 100644 index 00000000000..e23f7ce1f8f --- /dev/null +++ b/core/zms/src/main/rdl/TenantRoles.rdli @@ -0,0 +1,192 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +//The API for a provider service to grant tenant access, providing the roles. +include "Names.tdl"; + +//A representation of tenant role action. +type TenantRoleAction Struct { + SimpleName role; //name of the role + String action; //action value for the generated policy assertion +} + +//A representation of tenant roles to be provisioned. +type TenantRoles Struct { + DomainName domain; //name of the provider domain + SimpleName service; //name of the provider service + DomainName tenant; //name of the tenant domain + Array roles; //the role/action pairs to provision +} + +//A representation of tenant roles for resource groups to be provisioned. +type TenantResourceGroupRoles Struct { + DomainName domain; //name of the provider domain + SimpleName service; //name of the provider service + DomainName tenant; //name of the tenant domain + Array roles; //the role/action pairs to provision + EntityName resourceGroup; //tenant resource group +} + +//A representation of provider roles to be provisioned. +type ProviderResourceGroupRoles Struct { + DomainName domain; //name of the provider domain + SimpleName service; //name of the provider service + DomainName tenant; //name of the tenant domain + Array roles; //the role/action pairs to provision + EntityName resourceGroup; //tenant resource group +} + +//Create/update set of roles for a given tenant. +resource TenantRoles PUT "/domain/{domain}/service/{service}/tenant/{tenantDomain}" { + DomainName domain; //name of the provider domain + SimpleName service; //name of the provider service + DomainName tenantDomain; //name of the tenant domain + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + TenantRoles detail; //list of roles to be added/updated for the tenant + authorize ("update", "{domain}:tenant.{tenantDomain}"); + expected OK, CREATED; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Retrieve the configured set of roles for the tenant. +resource TenantRoles GET "/domain/{domain}/service/{service}/tenant/{tenantDomain}" { + DomainName domain; //name of the provider domain + SimpleName service; //name of the provider service + DomainName tenantDomain; //name of the tenant domain + authenticate; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Delete the configured set of roles for the tenant. Upon successful completion of +//this delete request, the server will return NO_CONTENT status code without any +//data (no object will be returned). +resource TenantRoles DELETE "/domain/{domain}/service/{service}/tenant/{tenantDomain}" { + DomainName domain; //name of the provider domain + SimpleName service; //name of the provider service + DomainName tenantDomain; //name of the tenant domain + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("delete", "{domain}:tenant.{tenantDomain}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Create/update set of roles for a given tenant and resource group +resource TenantResourceGroupRoles PUT "/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}" { + DomainName domain; //name of the provider domain + SimpleName service; //name of the provider service + DomainName tenantDomain; //name of the tenant domain + EntityName resourceGroup; //tenant resource group + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + TenantResourceGroupRoles detail; //list of roles to be added/updated for the tenant + authorize ("update", "{domain}:tenant.{tenantDomain}"); + expected OK, CREATED; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Retrieve the configured set of roles for the tenant and resource group +resource TenantResourceGroupRoles GET "/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}" { + DomainName domain; //name of the provider domain + SimpleName service; //name of the provider service + DomainName tenantDomain; //name of the tenant domain + EntityName resourceGroup; //tenant resource group + authenticate; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Delete the configured set of roles for the tenant and resource group +resource TenantResourceGroupRoles DELETE "/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}" { + DomainName domain; //name of the provider domain + SimpleName service; //name of the provider service + DomainName tenantDomain; //name of the tenant domain + EntityName resourceGroup; //tenant resource group + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("update", "{domain}:tenant.{tenantDomain}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Create/update set of roles for a given provider and resource group +resource ProviderResourceGroupRoles PUT "/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}" { + DomainName tenantDomain; //name of the tenant domain + DomainName provDomain; //name of the provider domain + SimpleName provService; //name of the provider service + EntityName resourceGroup; //tenant resource group + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + ProviderResourceGroupRoles detail; //list of roles to be added/updated for the provider + authorize ("update", "{tenantDomain}:tenancy.{provDomain}.{provService}"); + expected OK, CREATED; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} + +//Retrieve the configured set of roles for the provider and resource group +resource ProviderResourceGroupRoles GET "/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}" { + DomainName tenantDomain; //name of the tenant domain + DomainName provDomain; //name of the provider domain + SimpleName provService; //name of the provider service + EntityName resourceGroup; //tenant resource group + authenticate; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//Delete the configured set of roles for the provider and resource group +resource ProviderResourceGroupRoles DELETE "/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}" { + DomainName tenantDomain; //name of the tenant domain + DomainName provDomain; //name of the provider domain + SimpleName provService; //name of the provider service + EntityName resourceGroup; //tenant resource group + String auditRef (header="Y-Audit-Ref"); //Audit param required(not empty) if domain auditEnabled is true. + authorize ("update", "{tenantDomain}:tenancy.{provDomain}.{provService}"); + expected NO_CONTENT; + exceptions { + ResourceError NOT_FOUND; + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError CONFLICT; + } +} diff --git a/core/zms/src/main/rdl/Token.rdli b/core/zms/src/main/rdl/Token.rdli new file mode 100644 index 00000000000..bfcaaedd5af --- /dev/null +++ b/core/zms/src/main/rdl/Token.rdli @@ -0,0 +1,52 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; + +//A user token generated based on user's credentials +type UserToken Struct { + SignedToken token; //Signed user token identifying a specific authenticated user +} + +//Return a user/principal token for the specified authenticated user. Typical +//authenticated users with their native credentials are not allowed to update +//their domain data. They must first obtain a UserToken and then use that +//token for authentication and authorization of their update requests. +resource UserToken GET "/user/{userName}/token?services={serviceNames}" { + SimpleName userName; //name of the user + String serviceNames (optional); //comma separated list of on-behalf-of service names + authenticate; + exceptions { + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +//CORS (Cross-Origin Resource Sharing) support to allow Provider Services to obtain +//AuthorizedService Tokens on behalf of Tenant administrators +resource UserToken OPTIONS "/user/{userName}/token?services={serviceNames}" { + SimpleName userName; //name of the user + String serviceNames (optional); //comma separated list of on-behalf-of service names + exceptions { + ResourceError BAD_REQUEST; + } +} + +//A service principal object identifying a given service. +type ServicePrincipal Struct { + DomainName domain; //name of the domain + EntityName service; //name of the service + SignedToken token; //service's signed token +} + +//Return a ServicePrincipal object if the serviceToken is valid. This request +//provides a simple operation that an external application can execute to +//validate a service token. +resource ServicePrincipal GET "/principal" { + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} diff --git a/core/zms/src/main/rdl/ZMS.rdl b/core/zms/src/main/rdl/ZMS.rdl new file mode 100644 index 00000000000..816097f8213 --- /dev/null +++ b/core/zms/src/main/rdl/ZMS.rdl @@ -0,0 +1,23 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +// +// The Authorization Management Service (ZMS) Classes +// + +name ZMS; +version 1; +namespace com.yahoo.athenz.zms; + +include "Domain.rdli"; +include "DomainDataCheck.rdli"; +include "Entity.rdli"; +include "Role.rdli"; +include "Policy.rdli"; +include "ServiceIdentity.rdli"; +include "Tenancy.rdli"; +include "TenantRoles.rdli"; +include "Access.rdli"; +include "SignedDomains.rdli"; +include "Token.rdli"; +include "Template.rdli"; diff --git a/core/zms/src/test/java/com/yahoo/athenz/provider/ProviderSchemaTest.java b/core/zms/src/test/java/com/yahoo/athenz/provider/ProviderSchemaTest.java new file mode 100644 index 00000000000..c5ebbabb2c6 --- /dev/null +++ b/core/zms/src/test/java/com/yahoo/athenz/provider/ProviderSchemaTest.java @@ -0,0 +1,155 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.provider; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.testng.annotations.Test; + +import com.yahoo.rdl.Schema; +import com.yahoo.rdl.Validator; +import com.yahoo.rdl.Validator.Result; + +public class ProviderSchemaTest { + + @Test + public void test() { + Schema schema = ProviderSchema.instance(); + assertNotNull(schema); + } + + @Test + public void testTenants() { + + Schema schema = ProviderSchema.instance(); + Validator validator = new Validator(schema); + + Tenant t = new Tenant().setService("test-service").setName("test.domain").setState(TenantState.ACTIVE); + Result result = validator.validate(t, "Tenant"); + assertTrue(result.valid); + + t = new Tenant().setService("test_service").setName("test.domain").setState(TenantState.ACTIVE); + result = validator.validate(t, "Tenant"); + assertTrue(result.valid); + + t = new Tenant().setService("test@service").setName("test.domain").setState(TenantState.ACTIVE); + result = validator.validate(t, "Tenant"); + assertFalse(result.valid); + + } + + @Test + public void testTenantsMethods() { + + Schema schema = ProviderSchema.instance(); + Validator validator = new Validator(schema); + + List roles = Arrays.asList("test-role"); + List rg = Arrays.asList("test-resource-group"); + + Tenant t = new Tenant().setService("test-service").setName("test.domain").setState(TenantState.ACTIVE) + .setRoles(roles).setResourceGroups(rg); + Result result = validator.validate(t, "Tenant"); + assertTrue(result.valid); + + assertEquals(t.getService(), "test-service"); + assertEquals(t.getName(), "test.domain"); + assertEquals(t.getState(), TenantState.ACTIVE); + assertEquals(t.getRoles(), roles); + assertEquals(t.getResourceGroups(), rg); + assertEquals(t.getState(), TenantState.fromString("ACTIVE")); + + Tenant t2 = new Tenant().setService("test-service").setName("test.domain").setState(TenantState.ACTIVE) + .setRoles(roles).setResourceGroups(rg); + assertTrue(t.equals(t)); + + t.setResourceGroups(null); + assertFalse(t.equals(t2)); + t.setRoles(null); + assertFalse(t.equals(t2)); + t.setState(null); + assertFalse(t.equals(t2)); + t.setName(null); + assertFalse(t.equals(t2)); + t.setService(null); + assertFalse(t.equals(t2)); + + assertFalse(t.equals(new String())); + } + + @Test(expectedExceptions = { java.lang.IllegalArgumentException.class }) + public void testTenantStateException() { + TenantState.fromString("INVALID-STATE"); + } + + @Test + public void testTenantResourceGroup() { + + Schema schema = ProviderSchema.instance(); + Validator validator = new Validator(schema); + + TenantResourceGroup trg = new TenantResourceGroup().setService("test-service").setName("test.domain") + .setResourceGroup("test-group"); + Result result = validator.validate(trg, "TenantResourceGroup"); + assertTrue(result.valid); + + trg = new TenantResourceGroup().setService("test@service").setName("test.domain") + .setResourceGroup("test-group"); + result = validator.validate(trg, "TenantResourceGroup"); + assertFalse(result.valid); + + } + + @Test + public void testTenantResourceGroupMethods() { + Schema schema = ProviderSchema.instance(); + Validator validator = new Validator(schema); + + List roles = Arrays.asList("test-role"); + + TenantResourceGroup trg = new TenantResourceGroup().setService("test-service").setName("test.domain") + .setResourceGroup("test-group").setRoles(roles); + + Result result = validator.validate(trg, "TenantResourceGroup"); + assertTrue(result.valid); + + assertEquals(trg.getService(), "test-service"); + assertEquals(trg.getName(), "test.domain"); + assertEquals(trg.getResourceGroup(), "test-group"); + assertEquals(trg.getRoles(), roles); + + TenantResourceGroup trg2 = new TenantResourceGroup().setService("test-service").setName("test.domain") + .setResourceGroup("test-group").setRoles(roles); + + assertTrue(trg.equals(trg)); + trg.setRoles(null); + assertFalse(trg.equals(trg2)); + trg.setResourceGroup(null); + assertFalse(trg.equals(trg2)); + trg.setName(null); + assertFalse(trg.equals(trg2)); + trg.setService(null); + assertFalse(trg.equals(trg2)); + assertFalse(trg.equals(new String())); + + } +} diff --git a/core/zms/src/test/java/com/yahoo/athenz/zms/ZMSCoreTest.java b/core/zms/src/test/java/com/yahoo/athenz/zms/ZMSCoreTest.java new file mode 100644 index 00000000000..6f1424a1214 --- /dev/null +++ b/core/zms/src/test/java/com/yahoo/athenz/zms/ZMSCoreTest.java @@ -0,0 +1,1417 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zms; + +import com.yahoo.rdl.Schema; +import com.yahoo.rdl.Struct; +import com.yahoo.rdl.Timestamp; +import com.yahoo.rdl.UUID; +import com.yahoo.rdl.Validator; +import com.yahoo.rdl.Validator.Result; + +import static org.testng.Assert.*; +import org.testng.annotations.Test; +import java.util.List; +import java.util.Arrays; + +public class ZMSCoreTest { + + @Test + public void test() { + Schema schema = ZMSSchema.instance(); + assertNotNull(schema); + } + + @Test + public void testRoles() { + + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List members = Arrays.asList("user:boynton"); + Role r = new Role().setName("sys.auth:role.admin").setMembers(members); + Result result = validator.validate(r, "Role"); + assertTrue(result.valid); + + members = Arrays.asList("user.boynton"); // new + r = new Role().setName("sys.auth:role.admin").setMembers(members); + result = validator.validate(r, "Role"); + assertTrue(result.valid); + + members = Arrays.asList("someuser@somecompany.com"); // not a valid principal + r = new Role().setName("sys.auth:role.admin").setMembers(members); + result = validator.validate(r, "Role"); + assertFalse(result.valid); + } + + @Test + public void testRolesMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + // RoleAuditlog test + RoleAuditLog ral = new RoleAuditLog().setMember("user.test").setAdmin("user.admin") + .setCreated(Timestamp.fromMillis(123456789123L)).setAction("add").setAuditRef("zmstest"); + Result result = validator.validate(ral, "RoleAuditLog"); + assertTrue(result.valid); + + assertEquals(ral.getMember(), "user.test"); + assertEquals(ral.getAdmin(), "user.admin"); + assertEquals(ral.getCreated(), Timestamp.fromMillis(123456789123L)); + assertEquals(ral.getAction(), "add"); + assertEquals(ral.getAuditRef(), "zmstest"); + + RoleAuditLog ral2 = new RoleAuditLog().setMember("user.test").setAdmin("user.admin") + .setCreated(Timestamp.fromMillis(123456789123L)).setAction("add"); + assertTrue(ral.equals(ral)); + + ral2.setAuditRef(null); + assertFalse(ral2.equals(ral)); + ral2.setAction(null); + assertFalse(ral2.equals(ral)); + ral2.setCreated(null); + assertFalse(ral2.equals(ral)); + ral2.setAdmin(null); + assertFalse(ral2.equals(ral)); + ral2.setMember(null); + assertFalse(ral2.equals(ral)); + assertFalse(ral2.equals(new String())); + + List rall = Arrays.asList(ral); + + // Role test + List members = Arrays.asList("user:boynton"); + Role r = new Role().setName("sys.auth:role.admin").setMembers(members) + .setModified(Timestamp.fromMillis(123456789123L)).setTrust("domain.admin").setAuditLog(rall); + result = validator.validate(r, "Role"); + assertTrue(result.valid); + + assertEquals(r.getName(), "sys.auth:role.admin"); + assertEquals(r.getModified(), Timestamp.fromMillis(123456789123L)); + assertEquals(r.getMembers(), members); + assertEquals(r.getTrust(), "domain.admin"); + assertEquals(r.getAuditLog(), rall); + + Role r2 = new Role().setName("sys.auth:role.admin").setMembers(members) + .setModified(Timestamp.fromMillis(123456789123L)).setTrust("domain.admin"); + assertTrue(r.equals(r)); + + r2.setAuditLog(null); + assertFalse(r2.equals(r)); + r2.setTrust(null); + assertFalse(r2.equals(r)); + r2.setMembers(null); + assertFalse(r2.equals(r)); + r2.setModified(null); + assertFalse(r2.equals(r)); + r2.setName(null); + assertFalse(r2.equals(r)); + assertFalse(r.equals(new String())); + + List rl = Arrays.asList(r); + + // Roles test + Roles rs = new Roles().setList(rl); + result = validator.validate(rs, "Roles"); + assertTrue(result.valid); + + assertEquals(rs.getList(), rl); + + assertTrue(rs.equals(rs)); + assertFalse(rs.equals(new Roles())); + assertFalse(rs.equals(new String())); + + } + + @Test + public void testRoleListMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List names = Arrays.asList("test.role"); + + RoleList rl = new RoleList().setNames(names).setNext("next"); + + Result result = validator.validate(rl, "RoleList"); + assertTrue(result.valid); + + assertEquals(rl.getNames(), names); + assertEquals(rl.getNext(), "next"); + + RoleList rl2 = new RoleList().setNames(names); + + assertTrue(rl.equals(rl)); + + rl2.setNext(null); + assertFalse(rl2.equals(rl)); + rl2.setNames(null); + assertFalse(rl2.equals(rl)); + assertFalse(rl.equals(new String())); + } + + @Test + public void testSignedTokens() { + String[] signedTokens = { "v=R1;d=domain;s=test;i=127.0.0.1;h=someserver1.somecompany.com;r=role1,role2;s=signature", + "v=R1;d=domai_-.test;s=test---test;i=2001:db8:85a3:8d3:1319:8a2e:370:7348;h=hostname;r=role1,role2s=signature" }; + + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + System.out.println("Testing valid SignedTokens..."); + for (String s : signedTokens) { + System.out.println(s); + Result result = validator.validate(s, "SignedToken"); + assertTrue(result.valid); + } + } + + @Test + public void testYRN() { + String[] goodYRNs = { "domain:role.test1_", "domain:role._test1_", "domain:role._-test1_", "domain:role._-----", + "domain:role._____", "3com:role.3role_-", "3com:entity", "_domain:3entity_", "domain:entity", + "my.domain:entity", "my.domain:entity.path", "yrn:::domain:entity", "yrn:::my.domain:my.entity", + "yrn:service::domain:entity", "yrn:my.service::domain:entity", "yrn:my.service::my.domain:my.entity", + "yrn:service:location:domain:entity", "yrn:some.service:some.location:my.domain:my.entity" }; + + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + System.out.println("Testing valid YRNs..."); + for (String s : goodYRNs) { + System.out.println(s); + Result result = validator.validate(s, "YRN"); + assertTrue(result.valid); + } + + String[] badYRNs = { "domain:role.-----", "-domain:role.role1", "Non_ascii:��", "cannot-start-with:-dash", + "cannot-use:Punctuation_except_underbar!", "yrn::location_only", "yrn:service:location_only", + "non_yrn_prefix:service:location:domain:entity", "missing_yrn_prefix_service:location:domain:entity" }; + + System.out.println("Testing bad YRNs..."); + for (String s : badYRNs) { + System.out.println(s); + Result result = validator.validate(s, "YRN"); + assertFalse(result.valid); + } + } + + @Test + public void testSignedDomainsMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + Role r = new Role().setName("test.role"); + List rl = Arrays.asList(r); + + // assertion test + Assertion a = new Assertion().setRole("test.role.*").setResource("test.resource.*").setAction("test-action") + .setEffect(AssertionEffect.ALLOW).setId(0L); + Result result = validator.validate(a, "Assertion"); + assertTrue(result.valid); + + assertEquals(a.getRole(), "test.role.*"); + assertEquals(a.getResource(), "test.resource.*"); + assertEquals(a.getAction(), "test-action"); + assertEquals(a.getEffect(), AssertionEffect.fromString("ALLOW")); + assertEquals((long) a.getId(), 0L); + + Assertion a2 = new Assertion().setRole("test.role.*").setResource("test.resource.*").setAction("test-action") + .setEffect(AssertionEffect.ALLOW); + assertTrue(a.equals(a)); + + a2.setId(null); + assertFalse(a2.equals(a)); + a2.setEffect(null); + assertFalse(a2.equals(a)); + a2.setAction(null); + assertFalse(a2.equals(a)); + a2.setResource(null); + assertFalse(a2.equals(a)); + a2.setRole(null); + assertFalse(a2.equals(a)); + assertFalse(a.equals(new String())); + + List al = Arrays.asList(a); + + // Policy test + Policy p = new Policy().setName("test-policy").setModified(Timestamp.fromMillis(123456789123L)) + .setAssertions(al); + result = validator.validate(p, "Policy"); + assertTrue(result.valid); + + assertEquals(p.getName(), "test-policy"); + assertEquals(p.getModified(), Timestamp.fromMillis(123456789123L)); + assertEquals(p.getAssertions(), al); + + Policy p2 = new Policy().setName("test-policy").setModified(Timestamp.fromMillis(123456789123L)); + assertTrue(p.equals(p)); + + p2.setAssertions(null); + assertFalse(p2.equals(p)); + p2.setModified(null); + assertFalse(p2.equals(p)); + p2.setName(null); + assertFalse(p2.equals(p)); + assertFalse(p.equals(new String())); + + // PublicKeyEntry test + PublicKeyEntry pke = new PublicKeyEntry().setId("v1").setKey("pubkey===="); + result = validator.validate(pke, "PublicKeyEntry"); + assertTrue(result.valid); + + assertEquals(pke.getId(), "v1"); + assertEquals(pke.getKey(), "pubkey===="); + + PublicKeyEntry pke2 = new PublicKeyEntry().setKey("pubkey===="); + assertTrue(pke.equals(pke)); + + pke2.setId(null); + assertFalse(pke2.equals(pke)); + pke2.setKey(null); + assertFalse(pke2.equals(pke)); + assertFalse(pke.equals(new String())); + + // Entity test + Entity e = new Entity().setName("test.entity").setValue(new Struct().with("key", "test")); + result = validator.validate(e, "Entity"); + + assertEquals(e.getName(), "test.entity"); + assertEquals(e.getValue(), new Struct().with("key", (Object) "test")); + + Entity e2 = new Entity().setName("test.entity"); + assertTrue(e.equals(e)); + + e2.setValue(null); + assertFalse(e2.equals(e)); + e2.setName(null); + assertFalse(e2.equals(e)); + assertFalse(e.equals(new String())); + + List pl = Arrays.asList(p); + // DomainPolicies test + DomainPolicies dps = new DomainPolicies().setDomain("dps.domain").setPolicies(pl); + result = validator.validate(dps, "DomainPolicies"); + assertTrue(result.valid); + + assertEquals(dps.getDomain(), "dps.domain"); + assertEquals(dps.getPolicies(), pl); + + DomainPolicies dps2 = new DomainPolicies().setDomain("dps.domain"); + assertTrue(dps.equals(dps)); + + dps2.setPolicies(null); + assertFalse(dps2.equals(dps)); + dps2.setDomain(null); + assertFalse(dps2.equals(dps)); + assertFalse(dps.equals(new String())); + + // SignedPolicies test + SignedPolicies sp = new SignedPolicies().setContents(dps).setSignature("zmssignature").setKeyId("v1"); + result = validator.validate(sp, "SignedPolicies"); + assertTrue(result.valid); + + assertEquals(sp.getContents(), dps); + assertEquals(sp.getSignature(), "zmssignature"); + assertEquals(sp.getKeyId(), "v1"); + + SignedPolicies sp2 = new SignedPolicies().setContents(dps).setSignature("zmssignature"); + assertTrue(sp.equals(sp)); + + sp2.setKeyId(null); + assertFalse(sp2.equals(sp)); + sp2.setSignature(null); + assertFalse(sp2.equals(sp)); + sp2.setContents(null); + assertFalse(sp2.equals(sp)); + assertFalse(sp.equals(new String())); + + List pkel = Arrays.asList(pke); + List hosts = Arrays.asList("test.host"); + // ServiceIdentity test + ServiceIdentity si = new ServiceIdentity().setName("test.service").setPublicKeys(pkel) + .setProviderEndpoint("http://test.endpoint").setModified(Timestamp.fromMillis(123456789123L)) + .setExecutable("exec/path").setHosts(hosts).setUser("user.test").setGroup("test.group"); + result = validator.validate(si, "ServiceIdentity"); + assertTrue(result.valid); + + assertEquals(si.getName(), "test.service"); + assertEquals(si.getPublicKeys(), pkel); + assertEquals(si.getProviderEndpoint(), "http://test.endpoint"); + assertEquals(si.getModified(), Timestamp.fromMillis(123456789123L)); + assertEquals(si.getExecutable(), "exec/path"); + assertEquals(si.getHosts(), hosts); + assertEquals(si.getUser(), "user.test"); + assertEquals(si.getGroup(), "test.group"); + + ServiceIdentity si2 = new ServiceIdentity().setName("test.service").setPublicKeys(pkel) + .setProviderEndpoint("http://test.endpoint").setModified(Timestamp.fromMillis(123456789123L)) + .setExecutable("exec/path").setHosts(hosts).setUser("user.test"); + assertTrue(si.equals(si)); + + si2.setGroup(null); + assertFalse(si2.equals(si)); + si2.setUser(null); + assertFalse(si2.equals(si)); + si2.setHosts(null); + assertFalse(si2.equals(si)); + si2.setExecutable(null); + assertFalse(si2.equals(si)); + si2.setModified(null); + assertFalse(si2.equals(si)); + si2.setProviderEndpoint(null); + assertFalse(si2.equals(si)); + si2.setPublicKeys(null); + assertFalse(si2.equals(si)); + si2.setName(null); + assertFalse(si2.equals(si)); + assertFalse(si.equals(new String())); + + List sil = Arrays.asList(si); + List el = Arrays.asList(e); + + // ServiceIdentities test + ServiceIdentities sis = new ServiceIdentities().setList(sil); + result = validator.validate(sis, "ServiceIdentities"); + assertTrue(result.valid); + + assertEquals(sis.getList(), sil); + + assertTrue(sis.equals(sis)); + assertFalse(sis.equals(new ServiceIdentities())); + assertFalse(sis.equals(new String())); + + // DomainData test + DomainData dd = new DomainData().setName("test.domain").setAccount("user.test").setYpmId(1).setRoles(rl) + .setPolicies(sp).setServices(sil).setEntities(el).setModified(Timestamp.fromMillis(123456789123L)); + result = validator.validate(dd, "DomainData"); + assertTrue(result.valid); + + assertEquals(dd.getName(), "test.domain"); + assertEquals(dd.getAccount(), "user.test"); + assertEquals((int) dd.getYpmId(), 1); + assertEquals(dd.getRoles(), rl); + assertEquals(dd.getPolicies(), sp); + assertEquals(dd.getServices(), sil); + assertEquals(dd.getEntities(), el); + assertEquals(dd.getModified(), Timestamp.fromMillis(123456789123L)); + + DomainData dd2 = new DomainData().setName("test.domain").setAccount("user.test").setYpmId(1).setRoles(rl) + .setPolicies(sp).setServices(sil).setEntities(el); + assertTrue(dd.equals(dd)); + + dd2.setModified(null); + assertFalse(dd2.equals(dd)); + dd2.setEntities(null); + assertFalse(dd2.equals(dd)); + dd2.setServices(null); + assertFalse(dd2.equals(dd)); + dd2.setPolicies(null); + assertFalse(dd2.equals(dd)); + dd2.setRoles(null); + assertFalse(dd2.equals(dd)); + dd2.setYpmId(null); + assertFalse(dd2.equals(dd)); + dd2.setAccount(null); + assertFalse(dd2.equals(dd)); + dd2.setName(null); + assertFalse(dd2.equals(dd)); + assertFalse(dd.equals(new String())); + + // SignedDomain test + SignedDomain sd = new SignedDomain().setDomain(dd).setSignature("zmssignature").setKeyId("v1"); + result = validator.validate(sd, "SignedDomain"); + assertTrue(result.valid); + + assertEquals(sd.getDomain(), dd); + assertEquals(sd.getSignature(), "zmssignature"); + assertEquals(sd.getKeyId(), "v1"); + + SignedDomain sd2 = new SignedDomain().setDomain(dd).setSignature("zmssignature"); + assertTrue(sd.equals(sd)); + + sd2.setKeyId(null); + assertFalse(sd2.equals(sd)); + sd2.setSignature(null); + assertFalse(sd2.equals(sd)); + sd2.setDomain(null); + assertFalse(sd2.equals(sd)); + assertFalse(sd.equals(new String())); + + List sdl = Arrays.asList(sd); + // SignedDomains test + SignedDomains sds = new SignedDomains().setDomains(sdl); + result = validator.validate(sds, "SignedDomains"); + assertTrue(result.valid); + + assertEquals(sds.getDomains(), sdl); + + assertTrue(sds.equals(sds)); + assertFalse(sds.equals(new SignedDomains())); + assertFalse(sds.equals(new String())); + + } + + @Test + public void testAccess() { + Access a = new Access().setGranted(true); + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + Result result = validator.validate(a, "Access"); + assertTrue(result.valid); + + Access a2 = new Access().setGranted(false); + assertEquals(a.getGranted(), true); + + assertTrue(a.equals(a)); + + assertFalse(a.equals(a2)); + assertFalse(a.equals(new String())); + } + + @Test(expectedExceptions = { java.lang.IllegalArgumentException.class }) + public void testAssertionEffectExcept() { + AssertionEffect.fromString("INVALID EFFECT"); + } + + @Test + public void testDomainDataCheckMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List dlrl = Arrays.asList("test.role"); + + // DanglingPolicy test + DanglingPolicy dlp = new DanglingPolicy().setPolicyName("test.policy").setRoleName("test.role"); + Result result = validator.validate(dlp, "DanglingPolicy"); + assertTrue(result.valid); + + assertEquals(dlp.getPolicyName(), "test.policy"); + assertEquals(dlp.getRoleName(), "test.role"); + + DanglingPolicy dlp2 = new DanglingPolicy().setPolicyName("test.policy"); + assertTrue(dlp.equals(dlp)); + + dlp2.setRoleName(null); + assertFalse(dlp2.equals(dlp)); + dlp2.setPolicyName(null); + assertFalse(dlp2.equals(dlp)); + + List dlpl = Arrays.asList(dlp); + List pwt = Arrays.asList("provider.without.trust"); + List twar = Arrays.asList("tenants.without.assume.role"); + + DomainDataCheck ddc = new DomainDataCheck().setDanglingRoles(dlrl).setDanglingPolicies(dlpl).setPolicyCount(10) + .setAssertionCount(10).setRoleWildCardCount(10).setProvidersWithoutTrust(pwt) + .setTenantsWithoutAssumeRole(twar); + result = validator.validate(ddc, "DomainDataCheck"); + assertTrue(result.valid); + + assertEquals(ddc.getDanglingRoles(), dlrl); + assertEquals(ddc.getDanglingPolicies(), dlpl); + assertEquals(ddc.getPolicyCount(), 10); + assertEquals(ddc.getProvidersWithoutTrust(), pwt); + assertEquals(ddc.getTenantsWithoutAssumeRole(), twar); + assertEquals(ddc.getAssertionCount(), 10); + assertEquals(ddc.getRoleWildCardCount(), 10); + + DomainDataCheck ddc2 = new DomainDataCheck().setDanglingRoles(dlrl).setDanglingPolicies(dlpl).setPolicyCount(10) + .setAssertionCount(10).setRoleWildCardCount(10).setProvidersWithoutTrust(pwt); + assertTrue(ddc.equals(ddc)); + + ddc2.setTenantsWithoutAssumeRole(null); + assertFalse(ddc2.equals(ddc)); + ddc2.setProvidersWithoutTrust(null); + assertFalse(ddc2.equals(ddc)); + ddc2.setRoleWildCardCount(11); + assertFalse(ddc2.equals(ddc)); + ddc2.setAssertionCount(11); + assertFalse(ddc2.equals(ddc)); + ddc2.setPolicyCount(11); + assertFalse(ddc2.equals(ddc)); + ddc2.setDanglingPolicies(null); + assertFalse(ddc2.equals(ddc)); + ddc2.setDanglingRoles(null); + assertFalse(ddc2.equals(ddc)); + assertFalse(ddc2.equals(null)); + + } + + @Test + public void testDefaultAdmins() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List admins = Arrays.asList("user.admin"); + + DefaultAdmins da = new DefaultAdmins().setAdmins(admins); + Result result = validator.validate(da, "DefaultAdmins"); + assertTrue(result.valid); + + assertEquals(da.getAdmins(), admins); + + DefaultAdmins da2 = new DefaultAdmins().setAdmins(Arrays.asList("user.admin2")); + assertTrue(da.equals(da)); + + da2.setAdmins(null); + assertFalse(da2.equals(da)); + assertFalse(da2.equals(null)); + } + + @Test + public void testDomainMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + Domain d = new Domain().init(); + d.setName("test.domain").setModified(Timestamp.fromMillis(123456789123L)).setId(UUID.fromString("test-id")) + .setDescription("test desc").setOrg("test-org").setEnabled(true).setAuditEnabled(true) + .setAccount("user.test").setYpmId(1); + Result result = validator.validate(d, "Domain"); + assertTrue(result.valid); + + assertEquals(d.getName(), "test.domain"); + assertEquals(d.getModified(), Timestamp.fromMillis(123456789123L)); + assertEquals(d.getId(), UUID.fromString("test-id")); + assertEquals(d.getDescription(), "test desc"); + assertEquals(d.getOrg(), "test-org"); + assertTrue(d.getEnabled()); + assertTrue(d.getAuditEnabled()); + assertEquals(d.getAccount(), "user.test"); + assertEquals((int) d.getYpmId(), 1); + + Domain d2 = new Domain().setName("test.domain").setModified(Timestamp.fromMillis(123456789123L)) + .setId(UUID.fromString("test-id")).setDescription("test desc").setOrg("test-org").setEnabled(true) + .setAuditEnabled(true).setAccount("user.test"); + + assertTrue(d.equals(d)); + + d2.setYpmId(null); + assertFalse(d2.equals(d)); + d2.setAccount(null); + assertFalse(d2.equals(d)); + d2.setAuditEnabled(null); + assertFalse(d2.equals(d)); + d2.setEnabled(null); + assertFalse(d2.equals(d)); + d2.setOrg(null); + assertFalse(d2.equals(d)); + d2.setDescription(null); + assertFalse(d2.equals(d)); + d2.setId(null); + assertFalse(d2.equals(d)); + d2.setModified(null); + assertFalse(d2.equals(d)); + d2.setName(null); + assertFalse(d2.equals(d)); + assertFalse(d2.equals(null)); + assertFalse(d.equals(new String())); + + } + + @Test + public void testDomainList() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List domainnames = Arrays.asList("test.domain"); + + DomainList dl = new DomainList().setNames(domainnames).setNext("next"); + + Result result = validator.validate(dl, "DomainList"); + assertTrue(result.valid); + + assertEquals(dl.getNames(), domainnames); + assertEquals(dl.getNext(), "next"); + + DomainList dl2 = new DomainList().setNames(domainnames); + assertTrue(dl.equals(dl)); + + dl2.setNext(null); + assertFalse(dl2.equals(dl)); + dl2.setNames(null); + assertFalse(dl2.equals(dl)); + assertFalse(dl2.equals(null)); + assertFalse(dl.equals(new String())); + } + + @Test + public void testDomainMetaMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + DomainMeta dm = new DomainMeta().init(); + dm.setDescription("domain desc").setOrg("org:test").setEnabled(true).setAuditEnabled(false) + .setAccount("user.test").setYpmId(10); + + Result result = validator.validate(dm, "DomainMeta"); + assertTrue(result.valid); + + assertEquals(dm.getDescription(), "domain desc"); + assertEquals(dm.getOrg(), "org:test"); + assertTrue(dm.getEnabled()); + assertFalse(dm.getAuditEnabled()); + assertEquals(dm.getAccount(), "user.test"); + assertEquals((int) dm.getYpmId(), 10); + + DomainMeta dm2 = new DomainMeta().setDescription("domain desc").setOrg("org:test").setEnabled(true) + .setAuditEnabled(false).setAccount("user.test"); + assertTrue(dm.equals(dm)); + + dm2.setYpmId(null); + assertFalse(dm2.equals(dm)); + dm2.setAccount(null); + assertFalse(dm2.equals(dm)); + dm2.setAuditEnabled(null); + assertFalse(dm2.equals(dm)); + dm2.setEnabled(null); + assertFalse(dm2.equals(dm)); + dm2.setOrg(null); + assertFalse(dm2.equals(dm)); + dm2.setDescription(null); + assertFalse(dm2.equals(dm)); + assertFalse(dm2.equals(null)); + assertFalse(dm.equals(new String())); + + } + + @Test + public void testTopLevelDomainMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List admins = Arrays.asList("test.admin1"); + + // DomainTemplateList test + List templateNames = Arrays.asList("test"); + DomainTemplateList dtl = new DomainTemplateList().setTemplateNames(templateNames); + + Result result = validator.validate(dtl, "DomainTemplateList"); + assertTrue(result.valid); + + assertEquals(dtl.getTemplateNames(), templateNames); + assertTrue(dtl.equals(dtl)); + assertFalse(dtl.equals(new DomainTemplateList())); + + // TopLevelDomain test + TopLevelDomain tld = new TopLevelDomain().setDescription("domain desc").setOrg("org:test").setEnabled(true) + .setAuditEnabled(false).setAccount("user.test").setYpmId(10).setName("testdomain").setAdminUsers(admins) + .setTemplates(dtl); + + result = validator.validate(tld, "TopLevelDomain"); + assertTrue(result.valid); + + assertEquals(tld.getDescription(), "domain desc"); + assertEquals(tld.getOrg(), "org:test"); + assertTrue(tld.getEnabled()); + assertFalse(tld.getAuditEnabled()); + assertEquals(tld.getAccount(), "user.test"); + assertEquals((int) tld.getYpmId(), 10); + assertEquals(tld.getName(), "testdomain"); + assertEquals(tld.getAdminUsers(), admins); + assertNotNull(tld.getTemplates()); + + TopLevelDomain tld2 = new TopLevelDomain().setDescription("domain desc").setOrg("org:test").setEnabled(true) + .setAuditEnabled(false).setAccount("user.test").setYpmId(10).setName("testdomain") + .setAdminUsers(admins); + + assertTrue(tld.equals(tld)); + + tld2.setTemplates(null); + assertFalse(tld2.equals(tld)); + tld2.setAdminUsers(null); + assertFalse(tld2.equals(tld)); + tld2.setName(null); + assertFalse(tld2.equals(tld)); + tld2.setYpmId(null); + assertFalse(tld2.equals(tld)); + tld2.setAccount(null); + assertFalse(tld2.equals(tld)); + tld2.setAuditEnabled(null); + assertFalse(tld2.equals(tld)); + tld2.setEnabled(null); + assertFalse(tld2.equals(tld)); + tld2.setOrg(null); + assertFalse(tld2.equals(tld)); + tld2.setDescription(null); + assertFalse(tld2.equals(tld)); + assertFalse(tld2.equals(null)); + assertFalse(tld.equals(new String())); + } + + @Test + public void testSubDomainMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List admins = Arrays.asList("test.admin1"); + + SubDomain sd = new SubDomain().setDescription("domain desc").setOrg("org:test").setEnabled(true) + .setAuditEnabled(false).setAccount("user.test").setYpmId(10).setName("testdomain").setAdminUsers(admins) + .setTemplates(new DomainTemplateList().setTemplateNames(Arrays.asList("test.template"))) + .setParent("domain.parent"); + + Result result = validator.validate(sd, "SubDomain"); + assertTrue(result.valid); + + assertEquals(sd.getDescription(), "domain desc"); + assertEquals(sd.getOrg(), "org:test"); + assertTrue(sd.getEnabled()); + assertFalse(sd.getAuditEnabled()); + assertEquals(sd.getAccount(), "user.test"); + assertEquals((int) sd.getYpmId(), 10); + assertEquals(sd.getName(), "testdomain"); + assertEquals(sd.getAdminUsers(), admins); + assertNotNull(sd.getTemplates()); + assertEquals(sd.getParent(), "domain.parent"); + + SubDomain sd2 = new SubDomain().setDescription("domain desc").setOrg("org:test").setEnabled(true) + .setAuditEnabled(false).setAccount("user.test").setYpmId(10).setName("testdomain").setAdminUsers(admins) + .setTemplates(new DomainTemplateList().setTemplateNames(Arrays.asList("test.template"))) + .setParent("domain.parent2"); + + assertTrue(sd.equals(sd)); + + sd2.setParent(null); + assertFalse(sd2.equals(sd)); + sd2.setTemplates(null); + assertFalse(sd2.equals(sd)); + sd2.setAdminUsers(null); + assertFalse(sd2.equals(sd)); + sd2.setName(null); + assertFalse(sd2.equals(sd)); + sd2.setYpmId(null); + assertFalse(sd2.equals(sd)); + sd2.setAccount(null); + assertFalse(sd2.equals(sd)); + sd2.setAuditEnabled(null); + assertFalse(sd2.equals(sd)); + sd2.setEnabled(null); + assertFalse(sd2.equals(sd)); + sd2.setOrg(null); + assertFalse(sd2.equals(sd)); + sd2.setDescription(null); + assertFalse(sd2.equals(sd)); + assertFalse(sd2.equals(null)); + } + + @Test + public void testUserDomainMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + UserDomain ud = new UserDomain().setDescription("domain desc").setOrg("org:test").setEnabled(true) + .setAuditEnabled(false).setAccount("user.test").setYpmId(10).setName("testuser") + .setTemplates(new DomainTemplateList().setTemplateNames(Arrays.asList("template"))); + + Result result = validator.validate(ud, "UserDomain"); + assertTrue(result.valid); + + assertEquals(ud.getDescription(), "domain desc"); + assertEquals(ud.getOrg(), "org:test"); + assertTrue(ud.getEnabled()); + assertFalse(ud.getAuditEnabled()); + assertEquals(ud.getAccount(), "user.test"); + assertEquals((int) ud.getYpmId(), 10); + assertEquals(ud.getName(), "testuser"); + assertNotNull(ud.getTemplates()); + + UserDomain ud2 = new UserDomain().setDescription("domain desc").setOrg("org:test").setEnabled(true) + .setAuditEnabled(false).setAccount("user.test").setYpmId(10).setName("testuser"); + + assertTrue(ud.equals(ud)); + + ud2.setTemplates(null); + assertFalse(ud2.equals(ud)); + ud2.setName(null); + assertFalse(ud2.equals(ud)); + ud2.setYpmId(null); + assertFalse(ud2.equals(ud)); + ud2.setAccount(null); + assertFalse(ud2.equals(ud)); + ud2.setAuditEnabled(null); + assertFalse(ud2.equals(ud)); + ud2.setEnabled(null); + assertFalse(ud2.equals(ud)); + ud2.setOrg(null); + assertFalse(ud2.equals(ud)); + ud2.setDescription(null); + assertFalse(ud2.equals(ud)); + assertFalse(ud2.equals(null)); + assertFalse(ud.equals(new String())); + } + + @Test + public void testDomainModifiedListMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + // DomainModified test + DomainModified dm = new DomainModified().setName("test.domain").setModified(123456789123L); + + Result result = validator.validate(dm, "DomainModified"); + assertTrue(result.valid); + + assertEquals(dm.getName(), "test.domain"); + assertEquals(dm.getModified(), 123456789123L); + + DomainModified dm2 = new DomainModified().setName("test.domain"); + assertTrue(dm.equals(dm)); + + dm2.setModified(123456789124L); + assertFalse(dm2.equals(dm)); + dm2.setName(null); + assertFalse(dm2.equals(dm)); + assertFalse(dm2.equals(null)); + + // DomainModifiedList test + List dml = Arrays.asList(dm); + + DomainModifiedList dmlist = new DomainModifiedList().setNameModList(dml); + result = validator.validate(dmlist, "DomainModifiedList"); + assertTrue(result.valid); + + assertEquals(dmlist.getNameModList(), dml); + + assertTrue(dmlist.equals(dmlist)); + assertFalse(dmlist.equals(new DomainModifiedList())); + } + + @Test + public void testDomainTemplateMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List dl = Arrays.asList("user_provisioning"); + + DomainTemplate dt = new DomainTemplate().setTemplateNames(dl); + Result result = validator.validate(dt, "DomainTemplate"); + assertTrue(result.valid); + + assertEquals(dt.getTemplateNames(), dl); + + DomainTemplate dt2 = new DomainTemplate(); + assertTrue(dt.equals(dt)); + assertFalse(dt.equals(dt2)); + assertFalse(dt.equals(null)); + assertFalse(dt.equals(new String())); + } + + @Test + public void testMembershipMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + Membership ms = new Membership().init(); + ms.setMemberName("test.member").setIsMember(false).setRoleName("test.role"); + + Result result = validator.validate(ms, "Membership"); + assertTrue(result.valid); + + assertEquals(ms.getMemberName(), "test.member"); + assertFalse(ms.getIsMember()); + assertEquals(ms.getRoleName(), "test.role"); + + Membership ms2 = new Membership().setMemberName("test.member").setIsMember(false); + assertTrue(ms.equals(ms)); + + ms2.setRoleName(null); + assertFalse(ms2.equals(ms)); + ms2.setIsMember(null); + assertFalse(ms2.equals(ms)); + ms2.setMemberName(null); + assertFalse(ms2.equals(ms)); + assertFalse(ms2.equals(null)); + assertFalse(ms.equals(new String())); + + } + + @Test + public void testDefaultAdminsMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List dal = Arrays.asList("user.admin1"); + + DefaultAdmins da = new DefaultAdmins().setAdmins(dal); + + Result result = validator.validate(da, "DefaultAdmins"); + assertTrue(result.valid); + + assertEquals(da.getAdmins(), dal); + + DefaultAdmins da2 = new DefaultAdmins(); + assertTrue(da.equals(da)); + assertFalse(da.equals(da2)); + assertFalse(da.equals(null)); + + } + + @Test + public void testPolicyListMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List plist = Arrays.asList("test.policy"); + + PolicyList pl = new PolicyList().setNames(plist).setNext("next"); + + Result result = validator.validate(pl, "PolicyList"); + assertTrue(result.valid); + + assertEquals(pl.getNames(), plist); + assertEquals(pl.getNext(), "next"); + + PolicyList pl2 = new PolicyList().setNames(plist); + assertTrue(pl.equals(pl)); + + pl2.setNext(null); + assertFalse(pl2.equals(pl)); + pl2.setNames(null); + assertFalse(pl2.equals(pl)); + assertFalse(pl2.equals(null)); + + } + + @Test + public void testServiceIdentityListMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List slist = Arrays.asList("test.service"); + + ServiceIdentityList sil = new ServiceIdentityList().setNames(slist).setNext("next"); + + Result result = validator.validate(sil, "ServiceIdentityList"); + assertTrue(result.valid); + + assertEquals(sil.getNames(), slist); + assertEquals(sil.getNext(), "next"); + + ServiceIdentityList sil2 = new ServiceIdentityList().setNames(slist); + assertTrue(sil.equals(sil)); + + sil2.setNext(null); + assertFalse(sil2.equals(sil)); + sil2.setNames(null); + assertFalse(sil2.equals(sil)); + assertFalse(sil2.equals(null)); + assertFalse(sil.equals(new String())); + + } + + @Test + public void testTemplateListMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List tnames = Arrays.asList("testtemplate"); + + TemplateList tl = new TemplateList().setTemplateNames(tnames); + + Result result = validator.validate(tl, "TemplateList"); + assertTrue(result.valid); + + assertEquals(tl.getTemplateNames(), tnames); + + TemplateList tl2 = new TemplateList(); + assertTrue(tl.equals(tl)); + assertFalse(tl.equals(tl2)); + assertFalse(tl.equals(null)); + assertFalse(tl.equals(new String())); + + } + + @Test + public void testDomainTemplateListMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List tnames = Arrays.asList("testtemplate"); + + DomainTemplate tl = new DomainTemplate().setTemplateNames(tnames); + + Result result = validator.validate(tl, "DomainTemplate"); + assertTrue(result.valid); + + assertEquals(tl.getTemplateNames(), tnames); + + DomainTemplate tl2 = new DomainTemplate(); + assertTrue(tl.equals(tl)); + assertFalse(tl.equals(tl2)); + assertFalse(tl.equals(new String())); + + } + + @Test + public void testServerTemplateListMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List tnames = Arrays.asList("testtemplate"); + + ServerTemplateList tl = new ServerTemplateList().setTemplateNames(tnames); + + Result result = validator.validate(tl, "ServerTemplateList"); + assertTrue(result.valid); + + assertEquals(tl.getTemplateNames(), tnames); + + ServerTemplateList tl2 = new ServerTemplateList(); + assertTrue(tl.equals(tl)); + assertFalse(tl.equals(tl2)); + assertFalse(tl.equals(new String())); + + } + + @Test + public void testUserTokenMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + UserToken ut = new UserToken().setToken("testtoken"); + + Result result = validator.validate(ut, "UserToken"); + assertTrue(result.valid); + + assertEquals(ut.getToken(), "testtoken"); + + UserToken ut2 = new UserToken().setToken("test"); + assertTrue(ut.equals(ut)); + assertFalse(ut.equals(ut2)); + assertFalse(ut.equals(new String())); + } + + @Test + public void testTenantRolesMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + // TenantRoleAction test + TenantRoleAction tra = new TenantRoleAction().setRole("testrole").setAction("add"); + Result result = validator.validate(tra, "TenantRoleAction"); + assertTrue(result.valid); + + assertEquals(tra.getRole(), "testrole"); + assertEquals(tra.getAction(), "add"); + + TenantRoleAction tra2 = new TenantRoleAction().setRole("testrole"); + assertTrue(tra.equals(tra)); + + tra2.setAction(null); + assertFalse(tra2.equals(tra)); + tra2.setRole(null); + assertFalse(tra2.equals(tra)); + assertFalse(tra.equals(new String())); + + // TenantRoles test + List tral = Arrays.asList(tra); + TenantRoles tr = new TenantRoles().setDomain("test.provider.domain").setService("testservice") + .setTenant("test.tenant").setRoles(tral); + + result = validator.validate(tr, "TenantRoles"); + assertTrue(result.valid); + + assertEquals(tr.getDomain(), "test.provider.domain"); + assertEquals(tr.getService(), "testservice"); + assertEquals(tr.getTenant(), "test.tenant"); + assertEquals(tr.getRoles(), tral); + + TenantRoles tr2 = new TenantRoles().setDomain("test.provider.domain").setService("testservice") + .setTenant("test.tenant"); + assertTrue(tr.equals(tr)); + + tr2.setRoles(null); + assertFalse(tr2.equals(tr)); + tr2.setTenant(null); + assertFalse(tr2.equals(tr)); + tr2.setService(null); + assertFalse(tr2.equals(tr)); + tr2.setDomain(null); + assertFalse(tr2.equals(tr)); + assertFalse(tr2.equals(null)); + assertFalse(tr.equals(new String())); + + } + + @Test + public void testEntityListMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List elist = Arrays.asList("test.entity"); + + EntityList el = new EntityList().setNames(elist); + + Result result = validator.validate(el, "EntityList"); + assertTrue(result.valid); + + assertEquals(el.getNames(), elist); + + EntityList el2 = new EntityList(); + assertTrue(el.equals(el)); + assertFalse(el.equals(el2)); + assertFalse(el.equals(new String())); + + } + + @Test + public void testPoliciesMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + Assertion a = new Assertion().setRole("test.role.*").setResource("test.resource.*").setAction("test-action") + .setEffect(AssertionEffect.ALLOW).setId(0L); + + List plist = Arrays.asList(new Policy().setName("test").setAssertions(Arrays.asList(a))); + + Policies ps = new Policies().setList(plist); + + Result result = validator.validate(ps, "Policies"); + assertTrue(result.valid); + + assertEquals(ps.getList(), plist); + + Policies ps2 = new Policies(); + assertTrue(ps.equals(ps)); + assertFalse(ps.equals(ps2)); + assertFalse(ps.equals(new String())); + + } + + @Test + public void testTenancyMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List rg = Arrays.asList("test-resource"); + + Tenancy t = new Tenancy().setDomain("test.domain").setService("test-service").setResourceGroups(rg); + + Result result = validator.validate(t, "Tenancy"); + assertTrue(result.valid); + + assertEquals(t.getDomain(), "test.domain"); + assertEquals(t.getService(), "test-service"); + assertEquals(t.getResourceGroups(), rg); + + Tenancy t2 = new Tenancy().setDomain("test.domain").setService("test-service"); + assertTrue(t.equals(t)); + + t2.setResourceGroups(null); + assertFalse(t2.equals(t)); + t2.setService(null); + assertFalse(t2.equals(t)); + t2.setDomain(null); + assertFalse(t2.equals(t)); + assertFalse(t2.equals(null)); + assertFalse(t.equals(new String())); + + } + + @Test + public void testTenancyResourceGroupMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + TenancyResourceGroup trg = new TenancyResourceGroup().setDomain("test.domain").setService("test-service") + .setResourceGroup("test.group"); + + Result result = validator.validate(trg, "TenancyResourceGroup"); + assertTrue(result.valid); + + assertEquals(trg.getDomain(), "test.domain"); + assertEquals(trg.getService(), "test-service"); + assertEquals(trg.getResourceGroup(), "test.group"); + + TenancyResourceGroup trg2 = new TenancyResourceGroup().setDomain("test.domain").setService("test-service"); + assertTrue(trg.equals(trg)); + + trg2.setResourceGroup(null); + assertFalse(trg2.equals(trg)); + trg2.setService(null); + assertFalse(trg2.equals(trg)); + trg2.setDomain(null); + assertFalse(trg2.equals(trg)); + assertFalse(trg2.equals(null)); + assertFalse(trg2.equals(new String())); + } + + @Test + public void testResourceAccessListMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + Assertion a = new Assertion().setRole("test.role.*").setResource("test.resource.*").setAction("test-action") + .setEffect(AssertionEffect.ALLOW).setId(0L); + + List al = Arrays.asList(a); + // ResourceAccess test + ResourceAccess ra = new ResourceAccess().setPrincipal("test.principal").setAssertions(al); + + Result result = validator.validate(ra, "ResourceAccess"); + assertTrue(result.valid); + + assertEquals(ra.getPrincipal(), "test.principal"); + assertEquals(ra.getAssertions(), al); + + ResourceAccess ra2 = new ResourceAccess().setPrincipal("test.principal"); + assertTrue(ra.equals(ra)); + + ra2.setAssertions(null); + assertFalse(ra2.equals(ra)); + ra2.setPrincipal(null); + assertFalse(ra2.equals(ra)); + assertFalse(ra.equals(new String())); + + // ResourceAccessList test + List ralist = Arrays.asList(ra); + ResourceAccessList ral = new ResourceAccessList().setResources(ralist); + result = validator.validate(ral, "ResourceAccessList"); + assertTrue(result.valid); + + assertEquals(ral.getResources(), ralist); + + assertTrue(ral.equals(ral)); + assertFalse(ral.equals(new ResourceAccessList())); + assertFalse(ral.equals(null)); + } + + @Test + public void testServicePrincipalMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + ServicePrincipal sp = new ServicePrincipal().setDomain("test.domain").setService("test-service") + .setToken("test-token"); + + Result result = validator.validate(sp, "ServicePrincipal"); + assertTrue(result.valid); + + assertEquals(sp.getDomain(), "test.domain"); + assertEquals(sp.getService(), "test-service"); + assertEquals(sp.getToken(), "test-token"); + + ServicePrincipal sp2 = new ServicePrincipal().setDomain("test.domain").setService("test-service"); + assertTrue(sp.equals(sp)); + + sp2.setToken(null); + assertFalse(sp2.equals(sp)); + sp2.setService(null); + assertFalse(sp2.equals(sp)); + sp2.setDomain(null); + assertFalse(sp2.equals(sp)); + assertFalse(sp2.equals(null)); + assertFalse(sp.equals(new String())); + + } + + @Test + public void testTemplateMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + List rl = Arrays.asList(new Role().setName("sys.auth:role.admin").setMembers(Arrays.asList("test"))); + List pl = Arrays + .asList(new Policy().setName("test-policy").setModified(Timestamp.fromMillis(123456789123L))); + Template t = new Template().setRoles(rl).setPolicies(pl); + + Result result = validator.validate(t, "Template"); + assertTrue(result.valid); + + assertEquals(t.getPolicies(), pl); + assertEquals(t.getRoles(), rl); + + Template t2 = new Template().setRoles(rl); + assertTrue(t.equals(t)); + + t2.setRoles(null); + assertFalse(t2.equals(t)); + t2.setPolicies(null); + assertFalse(t2.equals(t)); + assertFalse(t2.equals(null)); + assertFalse(t.equals(new String())); + + } + + @Test + public void testProviderResourceGroupRolesMethod() { + Schema schema = ZMSSchema.instance(); + Validator validator = new Validator(schema); + + TenantRoleAction tra = new TenantRoleAction().setRole("testrole").setAction("add"); + List tral = Arrays.asList(tra); + + ProviderResourceGroupRoles prgr = new ProviderResourceGroupRoles().setDomain("test.domain") + .setService("test-service").setTenant("test.tenant").setRoles(tral).setResourceGroup("test-group"); + + Result result = validator.validate(prgr, "ProviderResourceGroupRoles"); + assertTrue(result.valid); + + assertEquals(prgr.getDomain(), "test.domain"); + assertEquals(prgr.getService(), "test-service"); + assertEquals(prgr.getTenant(), "test.tenant"); + assertEquals(prgr.getRoles(), tral); + assertEquals(prgr.getResourceGroup(), "test-group"); + + ProviderResourceGroupRoles prgr2 = new ProviderResourceGroupRoles().setDomain("test.domain") + .setService("test-service").setTenant("test.tenant").setRoles(tral); + assertTrue(prgr.equals(prgr)); + + prgr2.setResourceGroup(null); + assertFalse(prgr2.equals(prgr)); + prgr2.setRoles(null); + assertFalse(prgr2.equals(prgr)); + prgr2.setTenant(null); + assertFalse(prgr2.equals(prgr)); + prgr2.setService(null); + assertFalse(prgr2.equals(prgr)); + prgr2.setDomain(null); + assertFalse(prgr2.equals(prgr)); + assertFalse(prgr2.equals(null)); + assertFalse(prgr.equals(new String())); + + // TenantResourceGroupRoles test + TenantResourceGroupRoles trgr = new TenantResourceGroupRoles().setDomain("test.domain") + .setService("test-service").setTenant("test.domain").setRoles(tral).setResourceGroup("test.tenant"); + result = validator.validate(trgr, "TenantResourceGroupRoles"); + assertTrue(result.valid); + + assertEquals(trgr.getDomain(), "test.domain"); + assertEquals(trgr.getService(), "test-service"); + assertEquals(trgr.getTenant(), "test.domain"); + assertEquals(trgr.getRoles(), tral); + assertEquals(trgr.getResourceGroup(), "test.tenant"); + + TenantResourceGroupRoles trgr2 = new TenantResourceGroupRoles().setDomain("test.domain") + .setService("test-service").setTenant("test.domain").setRoles(tral); + + assertTrue(trgr.equals(trgr)); + + trgr2.setResourceGroup(null); + assertFalse(trgr2.equals(trgr)); + trgr2.setRoles(null); + assertFalse(trgr2.equals(trgr)); + trgr2.setTenant(null); + assertFalse(trgr2.equals(trgr)); + trgr2.setService(null); + assertFalse(trgr2.equals(trgr)); + trgr2.setDomain(null); + assertFalse(trgr2.equals(trgr)); + assertFalse(trgr.equals(new String())); + } + +} diff --git a/core/zms/src/test/resources/zms_private.pem b/core/zms/src/test/resources/zms_private.pem new file mode 100644 index 00000000000..356e1edf8bd --- /dev/null +++ b/core/zms/src/test/resources/zms_private.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTqa+RwOH3ZcMKyoGxOJo9A +yeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQJARpQBv09w/j6O7TmgtJm4 +Ws5bIxMgOkrHaqPs2Epq8rX8FUyaYSHBL4yYhqWlno4j1TrLzWneni8kJCUNJydU +AQIhAPXBXPQ3UzzNeQC5gbcweMMIQOYHyF7W/NAEILSKgy6hAiEAxL7hOjpdYGtj +hjG1nVIs+HW6z5TnRig7JjTwqA8RMdECIQC4oPCIuRfb0jJaDQQa8FuJiqXXK3mp +ZrLARJmdiYJMgQIgZxpAnWsIlAay2RgjvJXbyzim9TFrIXDjzlnf47JBqIECIBue +3ht9BnawOQqGzFjD89dFQ6nZtbiAaqORj276/SzZ +-----END RSA PRIVATE KEY----- diff --git a/core/zms/src/test/resources/zms_public.pem b/core/zms/src/test/resources/zms_public.pem new file mode 100644 index 00000000000..727c3c67f22 --- /dev/null +++ b/core/zms/src/test/resources/zms_public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTq +a+RwOH3ZcMKyoGxOJo9AyeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQ== +-----END PUBLIC KEY----- diff --git a/core/zts/README.md b/core/zts/README.md new file mode 100644 index 00000000000..38e18bbd825 --- /dev/null +++ b/core/zts/README.md @@ -0,0 +1,12 @@ +ZTS Core Support +================ + +Contains the ZTS interface and support classes. + +The interface, schema, and support classes are generated from the ZTS RDL definitions herein. Clients and servers will use these classes to implement the interface. + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/core/zts/pom.xml b/core/zts/pom.xml new file mode 100644 index 00000000000..15727fb2893 --- /dev/null +++ b/core/zts/pom.xml @@ -0,0 +1,67 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../pom.xml + + + zts_core + jar + zts_core + Core ZTS Interfaces + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.1.1 + + + + exec + + generate-sources + + + + bash + + scripts/make_stubs.sh + + + + + + + diff --git a/core/zts/scripts/make_stubs.sh b/core/zts/scripts/make_stubs.sh new file mode 100755 index 00000000000..3e5c9ee25bf --- /dev/null +++ b/core/zts/scripts/make_stubs.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# If any of the RDL files have been updated, then this script should be run +# rdl to generate the appropriate model classes. + +# Note this script is dependent on the rdl utility. +# go get github.com/ardielle/ardielle-tools/... +# + +command -v rdl >/dev/null 2>&1 || { + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "SOURCE WARNING"; + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "Please install rdl utility: go get github.com/ardielle/ardielle-tools/..."; + echo >&2 "Skipping source generation..."; + exit 0; +} + +RDL_ZTS_FILE=src/main/rdl/ZTS.rdl + +echo "Generating model classes..." +rdl -s generate -x getsetters=true -o src/main/java java-model $RDL_ZTS_FILE + +# Copyright 2016 Yahoo Inc. +# Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. \ No newline at end of file diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/AWSCertificateRequest.java b/core/zts/src/main/java/com/yahoo/athenz/zts/AWSCertificateRequest.java new file mode 100644 index 00000000000..91168231270 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/AWSCertificateRequest.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// AWSCertificateRequest - AWSCertificateRequest - a certificate signing +// request +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class AWSCertificateRequest { + public String csr; + + public AWSCertificateRequest setCsr(String csr) { + this.csr = csr; + return this; + } + public String getCsr() { + return csr; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != AWSCertificateRequest.class) { + return false; + } + AWSCertificateRequest a = (AWSCertificateRequest) another; + if (csr == null ? a.csr != null : !csr.equals(a.csr)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/AWSInstanceInformation.java b/core/zts/src/main/java/com/yahoo/athenz/zts/AWSInstanceInformation.java new file mode 100644 index 00000000000..59ac80580cb --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/AWSInstanceInformation.java @@ -0,0 +1,193 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// AWSInstanceInformation - AWSInstanceInformation - the information a booting +// EC2 instance must provide to ZTS to authenticate. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class AWSInstanceInformation { + public String document; + public String signature; + public String domain; + public String service; + public String csr; + public String name; + public String account; + @RdlOptional + public String cloud; + public String subnet; + public String access; + public String secret; + public String token; + public Timestamp expires; + public Timestamp modified; + public String flavor; + + public AWSInstanceInformation setDocument(String document) { + this.document = document; + return this; + } + public String getDocument() { + return document; + } + public AWSInstanceInformation setSignature(String signature) { + this.signature = signature; + return this; + } + public String getSignature() { + return signature; + } + public AWSInstanceInformation setDomain(String domain) { + this.domain = domain; + return this; + } + public String getDomain() { + return domain; + } + public AWSInstanceInformation setService(String service) { + this.service = service; + return this; + } + public String getService() { + return service; + } + public AWSInstanceInformation setCsr(String csr) { + this.csr = csr; + return this; + } + public String getCsr() { + return csr; + } + public AWSInstanceInformation setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public AWSInstanceInformation setAccount(String account) { + this.account = account; + return this; + } + public String getAccount() { + return account; + } + public AWSInstanceInformation setCloud(String cloud) { + this.cloud = cloud; + return this; + } + public String getCloud() { + return cloud; + } + public AWSInstanceInformation setSubnet(String subnet) { + this.subnet = subnet; + return this; + } + public String getSubnet() { + return subnet; + } + public AWSInstanceInformation setAccess(String access) { + this.access = access; + return this; + } + public String getAccess() { + return access; + } + public AWSInstanceInformation setSecret(String secret) { + this.secret = secret; + return this; + } + public String getSecret() { + return secret; + } + public AWSInstanceInformation setToken(String token) { + this.token = token; + return this; + } + public String getToken() { + return token; + } + public AWSInstanceInformation setExpires(Timestamp expires) { + this.expires = expires; + return this; + } + public Timestamp getExpires() { + return expires; + } + public AWSInstanceInformation setModified(Timestamp modified) { + this.modified = modified; + return this; + } + public Timestamp getModified() { + return modified; + } + public AWSInstanceInformation setFlavor(String flavor) { + this.flavor = flavor; + return this; + } + public String getFlavor() { + return flavor; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != AWSInstanceInformation.class) { + return false; + } + AWSInstanceInformation a = (AWSInstanceInformation) another; + if (document == null ? a.document != null : !document.equals(a.document)) { + return false; + } + if (signature == null ? a.signature != null : !signature.equals(a.signature)) { + return false; + } + if (domain == null ? a.domain != null : !domain.equals(a.domain)) { + return false; + } + if (service == null ? a.service != null : !service.equals(a.service)) { + return false; + } + if (csr == null ? a.csr != null : !csr.equals(a.csr)) { + return false; + } + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (account == null ? a.account != null : !account.equals(a.account)) { + return false; + } + if (cloud == null ? a.cloud != null : !cloud.equals(a.cloud)) { + return false; + } + if (subnet == null ? a.subnet != null : !subnet.equals(a.subnet)) { + return false; + } + if (access == null ? a.access != null : !access.equals(a.access)) { + return false; + } + if (secret == null ? a.secret != null : !secret.equals(a.secret)) { + return false; + } + if (token == null ? a.token != null : !token.equals(a.token)) { + return false; + } + if (expires == null ? a.expires != null : !expires.equals(a.expires)) { + return false; + } + if (modified == null ? a.modified != null : !modified.equals(a.modified)) { + return false; + } + if (flavor == null ? a.flavor != null : !flavor.equals(a.flavor)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/AWSTemporaryCredentials.java b/core/zts/src/main/java/com/yahoo/athenz/zts/AWSTemporaryCredentials.java new file mode 100644 index 00000000000..beb6acf1abf --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/AWSTemporaryCredentials.java @@ -0,0 +1,70 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// AWSTemporaryCredentials - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class AWSTemporaryCredentials { + public String accessKeyId; + public String secretAccessKey; + public String sessionToken; + public Timestamp expiration; + + public AWSTemporaryCredentials setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + return this; + } + public String getAccessKeyId() { + return accessKeyId; + } + public AWSTemporaryCredentials setSecretAccessKey(String secretAccessKey) { + this.secretAccessKey = secretAccessKey; + return this; + } + public String getSecretAccessKey() { + return secretAccessKey; + } + public AWSTemporaryCredentials setSessionToken(String sessionToken) { + this.sessionToken = sessionToken; + return this; + } + public String getSessionToken() { + return sessionToken; + } + public AWSTemporaryCredentials setExpiration(Timestamp expiration) { + this.expiration = expiration; + return this; + } + public Timestamp getExpiration() { + return expiration; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != AWSTemporaryCredentials.class) { + return false; + } + AWSTemporaryCredentials a = (AWSTemporaryCredentials) another; + if (accessKeyId == null ? a.accessKeyId != null : !accessKeyId.equals(a.accessKeyId)) { + return false; + } + if (secretAccessKey == null ? a.secretAccessKey != null : !secretAccessKey.equals(a.secretAccessKey)) { + return false; + } + if (sessionToken == null ? a.sessionToken != null : !sessionToken.equals(a.sessionToken)) { + return false; + } + if (expiration == null ? a.expiration != null : !expiration.equals(a.expiration)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/Access.java b/core/zts/src/main/java/com/yahoo/athenz/zts/Access.java new file mode 100644 index 00000000000..1bd70443453 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/Access.java @@ -0,0 +1,37 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Access - Access can be checked and returned as this resource. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Access { + public boolean granted; + + public Access setGranted(boolean granted) { + this.granted = granted; + return this; + } + public boolean getGranted() { + return granted; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Access.class) { + return false; + } + Access a = (Access) another; + if (granted != a.granted) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/Assertion.java b/core/zts/src/main/java/com/yahoo/athenz/zts/Assertion.java new file mode 100644 index 00000000000..3f9e276102a --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/Assertion.java @@ -0,0 +1,84 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Assertion - A representation for the encapsulation of an action to be +// performed on a resource by a principal. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Assertion { + public String role; + public String resource; + public String action; + @RdlOptional + public AssertionEffect effect; + @RdlOptional + public Long id; + + public Assertion setRole(String role) { + this.role = role; + return this; + } + public String getRole() { + return role; + } + public Assertion setResource(String resource) { + this.resource = resource; + return this; + } + public String getResource() { + return resource; + } + public Assertion setAction(String action) { + this.action = action; + return this; + } + public String getAction() { + return action; + } + public Assertion setEffect(AssertionEffect effect) { + this.effect = effect; + return this; + } + public AssertionEffect getEffect() { + return effect; + } + public Assertion setId(Long id) { + this.id = id; + return this; + } + public Long getId() { + return id; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Assertion.class) { + return false; + } + Assertion a = (Assertion) another; + if (role == null ? a.role != null : !role.equals(a.role)) { + return false; + } + if (resource == null ? a.resource != null : !resource.equals(a.resource)) { + return false; + } + if (action == null ? a.action != null : !action.equals(a.action)) { + return false; + } + if (effect == null ? a.effect != null : !effect.equals(a.effect)) { + return false; + } + if (id == null ? a.id != null : !id.equals(a.id)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/AssertionEffect.java b/core/zts/src/main/java/com/yahoo/athenz/zts/AssertionEffect.java new file mode 100644 index 00000000000..a4c5e5df64b --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/AssertionEffect.java @@ -0,0 +1,23 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; + +// +// AssertionEffect - Every assertion can have the effect of ALLOW or DENY. +// +public enum AssertionEffect { + ALLOW, + DENY; + + public static AssertionEffect fromString(String v) { + for (AssertionEffect e : values()) { + if (e.toString().equals(v)) { + return e; + } + } + throw new IllegalArgumentException("Invalid string representation for AssertionEffect: " + v); + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetric.java b/core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetric.java new file mode 100644 index 00000000000..72ee69031b4 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetric.java @@ -0,0 +1,48 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainMetric - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainMetric { + public DomainMetricType metricType; + public int metricVal; + + public DomainMetric setMetricType(DomainMetricType metricType) { + this.metricType = metricType; + return this; + } + public DomainMetricType getMetricType() { + return metricType; + } + public DomainMetric setMetricVal(int metricVal) { + this.metricVal = metricVal; + return this; + } + public int getMetricVal() { + return metricVal; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainMetric.class) { + return false; + } + DomainMetric a = (DomainMetric) another; + if (metricType == null ? a.metricType != null : !metricType.equals(a.metricType)) { + return false; + } + if (metricVal != a.metricVal) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetricType.java b/core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetricType.java new file mode 100644 index 00000000000..77a72103420 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetricType.java @@ -0,0 +1,39 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; + +// +// DomainMetricType - zpe metric attributes +// +public enum DomainMetricType { + ACCESS_ALLOWED, + ACCESS_ALLOWED_DENY, + ACCESS_ALLOWED_DENY_NO_MATCH, + ACCESS_ALLOWED_ALLOW, + ACCESS_ALLOWED_ERROR, + ACCESS_ALLOWED_TOKEN_INVALID, + ACCESS_Allowed_TOKEN_EXPIRED, + ACCESS_ALLOWED_DOMAIN_NOT_FOUND, + ACCESS_ALLOWED_DOMAIN_MISMATCH, + ACCESS_ALLOWED_DOMAIN_EXPIRED, + ACCESS_ALLOWED_DOMAIN_EMPTY, + ACCESS_ALLOWED_TOKEN_CACHE_FAILURE, + ACCESS_ALLOWED_TOKEN_CACHE_NOT_FOUND, + ACCESS_ALLOWED_TOKEN_CACHE_SUCCESS, + ACCESS_ALLOWED_TOKEN_VALIDATE, + LOAD_FILE_FAIL, + LOAD_FILE_GOOD, + LOAD_DOMAIN_GOOD; + + public static DomainMetricType fromString(String v) { + for (DomainMetricType e : values()) { + if (e.toString().equals(v)) { + return e; + } + } + throw new IllegalArgumentException("Invalid string representation for DomainMetricType: " + v); + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetrics.java b/core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetrics.java new file mode 100644 index 00000000000..68b5c79f2d3 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/DomainMetrics.java @@ -0,0 +1,49 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainMetrics - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainMetrics { + public String domainName; + public List metricList; + + public DomainMetrics setDomainName(String domainName) { + this.domainName = domainName; + return this; + } + public String getDomainName() { + return domainName; + } + public DomainMetrics setMetricList(List metricList) { + this.metricList = metricList; + return this; + } + public List getMetricList() { + return metricList; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainMetrics.class) { + return false; + } + DomainMetrics a = (DomainMetrics) another; + if (domainName == null ? a.domainName != null : !domainName.equals(a.domainName)) { + return false; + } + if (metricList == null ? a.metricList != null : !metricList.equals(a.metricList)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/DomainSignedPolicyData.java b/core/zts/src/main/java/com/yahoo/athenz/zts/DomainSignedPolicyData.java new file mode 100644 index 00000000000..cf6b3583d5f --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/DomainSignedPolicyData.java @@ -0,0 +1,60 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// DomainSignedPolicyData - A signed bulk transfer of policies. The data is +// signed with server's private key. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class DomainSignedPolicyData { + public SignedPolicyData signedPolicyData; + public String signature; + public String keyId; + + public DomainSignedPolicyData setSignedPolicyData(SignedPolicyData signedPolicyData) { + this.signedPolicyData = signedPolicyData; + return this; + } + public SignedPolicyData getSignedPolicyData() { + return signedPolicyData; + } + public DomainSignedPolicyData setSignature(String signature) { + this.signature = signature; + return this; + } + public String getSignature() { + return signature; + } + public DomainSignedPolicyData setKeyId(String keyId) { + this.keyId = keyId; + return this; + } + public String getKeyId() { + return keyId; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != DomainSignedPolicyData.class) { + return false; + } + DomainSignedPolicyData a = (DomainSignedPolicyData) another; + if (signedPolicyData == null ? a.signedPolicyData != null : !signedPolicyData.equals(a.signedPolicyData)) { + return false; + } + if (signature == null ? a.signature != null : !signature.equals(a.signature)) { + return false; + } + if (keyId == null ? a.keyId != null : !keyId.equals(a.keyId)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/HostServices.java b/core/zts/src/main/java/com/yahoo/athenz/zts/HostServices.java new file mode 100644 index 00000000000..7da23cf3342 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/HostServices.java @@ -0,0 +1,50 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// HostServices - The representation for an enumeration of services authorized +// to run on a specific host. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class HostServices { + public String host; + public List names; + + public HostServices setHost(String host) { + this.host = host; + return this; + } + public String getHost() { + return host; + } + public HostServices setNames(List names) { + this.names = names; + return this; + } + public List getNames() { + return names; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != HostServices.class) { + return false; + } + HostServices a = (HostServices) another; + if (host == null ? a.host != null : !host.equals(a.host)) { + return false; + } + if (names == null ? a.names != null : !names.equals(a.names)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/Identity.java b/core/zts/src/main/java/com/yahoo/athenz/zts/Identity.java new file mode 100644 index 00000000000..11094dc57ce --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/Identity.java @@ -0,0 +1,100 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import java.util.Map; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Identity - Identity - a signed assertion of service or human identity, the +// response could be either a client certificate or just a regular NToken +// (depending if the request contained a csr or not). +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Identity { + public String name; + @RdlOptional + public String certificate; + @RdlOptional + public String caCertBundle; + @RdlOptional + public String sshServerCert; + @RdlOptional + public String serviceToken; + @RdlOptional + public Map attributes; + + public Identity setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public Identity setCertificate(String certificate) { + this.certificate = certificate; + return this; + } + public String getCertificate() { + return certificate; + } + public Identity setCaCertBundle(String caCertBundle) { + this.caCertBundle = caCertBundle; + return this; + } + public String getCaCertBundle() { + return caCertBundle; + } + public Identity setSshServerCert(String sshServerCert) { + this.sshServerCert = sshServerCert; + return this; + } + public String getSshServerCert() { + return sshServerCert; + } + public Identity setServiceToken(String serviceToken) { + this.serviceToken = serviceToken; + return this; + } + public String getServiceToken() { + return serviceToken; + } + public Identity setAttributes(Map attributes) { + this.attributes = attributes; + return this; + } + public Map getAttributes() { + return attributes; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Identity.class) { + return false; + } + Identity a = (Identity) another; + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (certificate == null ? a.certificate != null : !certificate.equals(a.certificate)) { + return false; + } + if (caCertBundle == null ? a.caCertBundle != null : !caCertBundle.equals(a.caCertBundle)) { + return false; + } + if (sshServerCert == null ? a.sshServerCert != null : !sshServerCert.equals(a.sshServerCert)) { + return false; + } + if (serviceToken == null ? a.serviceToken != null : !serviceToken.equals(a.serviceToken)) { + return false; + } + if (attributes == null ? a.attributes != null : !attributes.equals(a.attributes)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/InstanceInformation.java b/core/zts/src/main/java/com/yahoo/athenz/zts/InstanceInformation.java new file mode 100644 index 00000000000..1bacaa9a017 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/InstanceInformation.java @@ -0,0 +1,94 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// InstanceInformation - Instance object that includes requested service +// details plus host document that is signed by provider as part of the host +// bootstrap process +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class InstanceInformation { + public String document; + public String signature; + public String keyId; + public String domain; + public String service; + public String csr; + + public InstanceInformation setDocument(String document) { + this.document = document; + return this; + } + public String getDocument() { + return document; + } + public InstanceInformation setSignature(String signature) { + this.signature = signature; + return this; + } + public String getSignature() { + return signature; + } + public InstanceInformation setKeyId(String keyId) { + this.keyId = keyId; + return this; + } + public String getKeyId() { + return keyId; + } + public InstanceInformation setDomain(String domain) { + this.domain = domain; + return this; + } + public String getDomain() { + return domain; + } + public InstanceInformation setService(String service) { + this.service = service; + return this; + } + public String getService() { + return service; + } + public InstanceInformation setCsr(String csr) { + this.csr = csr; + return this; + } + public String getCsr() { + return csr; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != InstanceInformation.class) { + return false; + } + InstanceInformation a = (InstanceInformation) another; + if (document == null ? a.document != null : !document.equals(a.document)) { + return false; + } + if (signature == null ? a.signature != null : !signature.equals(a.signature)) { + return false; + } + if (keyId == null ? a.keyId != null : !keyId.equals(a.keyId)) { + return false; + } + if (domain == null ? a.domain != null : !domain.equals(a.domain)) { + return false; + } + if (service == null ? a.service != null : !service.equals(a.service)) { + return false; + } + if (csr == null ? a.csr != null : !csr.equals(a.csr)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/InstanceRefreshRequest.java b/core/zts/src/main/java/com/yahoo/athenz/zts/InstanceRefreshRequest.java new file mode 100644 index 00000000000..a9d3d37c6b4 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/InstanceRefreshRequest.java @@ -0,0 +1,50 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// InstanceRefreshRequest - InstanceRefreshRequest - a certificate refresh +// request +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class InstanceRefreshRequest { + public String csr; + @RdlOptional + public Integer expiryTime; + + public InstanceRefreshRequest setCsr(String csr) { + this.csr = csr; + return this; + } + public String getCsr() { + return csr; + } + public InstanceRefreshRequest setExpiryTime(Integer expiryTime) { + this.expiryTime = expiryTime; + return this; + } + public Integer getExpiryTime() { + return expiryTime; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != InstanceRefreshRequest.class) { + return false; + } + InstanceRefreshRequest a = (InstanceRefreshRequest) another; + if (csr == null ? a.csr != null : !csr.equals(a.csr)) { + return false; + } + if (expiryTime == null ? a.expiryTime != null : !expiryTime.equals(a.expiryTime)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/Policy.java b/core/zts/src/main/java/com/yahoo/athenz/zts/Policy.java new file mode 100644 index 00000000000..939b80287d8 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/Policy.java @@ -0,0 +1,61 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// Policy - The representation for a Policy with set of assertions. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class Policy { + public String name; + @RdlOptional + public Timestamp modified; + public List assertions; + + public Policy setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public Policy setModified(Timestamp modified) { + this.modified = modified; + return this; + } + public Timestamp getModified() { + return modified; + } + public Policy setAssertions(List assertions) { + this.assertions = assertions; + return this; + } + public List getAssertions() { + return assertions; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != Policy.class) { + return false; + } + Policy a = (Policy) another; + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (modified == null ? a.modified != null : !modified.equals(a.modified)) { + return false; + } + if (assertions == null ? a.assertions != null : !assertions.equals(a.assertions)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/PolicyData.java b/core/zts/src/main/java/com/yahoo/athenz/zts/PolicyData.java new file mode 100644 index 00000000000..66da2d5ec0c --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/PolicyData.java @@ -0,0 +1,49 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// PolicyData - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class PolicyData { + public String domain; + public List policies; + + public PolicyData setDomain(String domain) { + this.domain = domain; + return this; + } + public String getDomain() { + return domain; + } + public PolicyData setPolicies(List policies) { + this.policies = policies; + return this; + } + public List getPolicies() { + return policies; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != PolicyData.class) { + return false; + } + PolicyData a = (PolicyData) another; + if (domain == null ? a.domain != null : !domain.equals(a.domain)) { + return false; + } + if (policies == null ? a.policies != null : !policies.equals(a.policies)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/PublicKeyEntry.java b/core/zts/src/main/java/com/yahoo/athenz/zts/PublicKeyEntry.java new file mode 100644 index 00000000000..e512a93b96b --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/PublicKeyEntry.java @@ -0,0 +1,49 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// PublicKeyEntry - The representation of the public key in a service identity +// object. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class PublicKeyEntry { + public String key; + public String id; + + public PublicKeyEntry setKey(String key) { + this.key = key; + return this; + } + public String getKey() { + return key; + } + public PublicKeyEntry setId(String id) { + this.id = id; + return this; + } + public String getId() { + return id; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != PublicKeyEntry.class) { + return false; + } + PublicKeyEntry a = (PublicKeyEntry) another; + if (key == null ? a.key != null : !key.equals(a.key)) { + return false; + } + if (id == null ? a.id != null : !id.equals(a.id)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/RoleAccess.java b/core/zts/src/main/java/com/yahoo/athenz/zts/RoleAccess.java new file mode 100644 index 00000000000..86a92df82f7 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/RoleAccess.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// RoleAccess - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class RoleAccess { + public List roles; + + public RoleAccess setRoles(List roles) { + this.roles = roles; + return this; + } + public List getRoles() { + return roles; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != RoleAccess.class) { + return false; + } + RoleAccess a = (RoleAccess) another; + if (roles == null ? a.roles != null : !roles.equals(a.roles)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/RoleToken.java b/core/zts/src/main/java/com/yahoo/athenz/zts/RoleToken.java new file mode 100644 index 00000000000..8d768a79405 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/RoleToken.java @@ -0,0 +1,48 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// RoleToken - A representation of a signed RoleToken +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class RoleToken { + public String token; + public long expiryTime; + + public RoleToken setToken(String token) { + this.token = token; + return this; + } + public String getToken() { + return token; + } + public RoleToken setExpiryTime(long expiryTime) { + this.expiryTime = expiryTime; + return this; + } + public long getExpiryTime() { + return expiryTime; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != RoleToken.class) { + return false; + } + RoleToken a = (RoleToken) another; + if (token == null ? a.token != null : !token.equals(a.token)) { + return false; + } + if (expiryTime != a.expiryTime) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/ServiceIdentity.java b/core/zts/src/main/java/com/yahoo/athenz/zts/ServiceIdentity.java new file mode 100644 index 00000000000..b5c0fcf5392 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/ServiceIdentity.java @@ -0,0 +1,122 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// ServiceIdentity - The representation of the service identity object. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class ServiceIdentity { + public String name; + @RdlOptional + public List publicKeys; + @RdlOptional + public String providerEndpoint; + @RdlOptional + public Timestamp modified; + @RdlOptional + public String executable; + @RdlOptional + public List hosts; + @RdlOptional + public String user; + @RdlOptional + public String group; + + public ServiceIdentity setName(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + public ServiceIdentity setPublicKeys(List publicKeys) { + this.publicKeys = publicKeys; + return this; + } + public List getPublicKeys() { + return publicKeys; + } + public ServiceIdentity setProviderEndpoint(String providerEndpoint) { + this.providerEndpoint = providerEndpoint; + return this; + } + public String getProviderEndpoint() { + return providerEndpoint; + } + public ServiceIdentity setModified(Timestamp modified) { + this.modified = modified; + return this; + } + public Timestamp getModified() { + return modified; + } + public ServiceIdentity setExecutable(String executable) { + this.executable = executable; + return this; + } + public String getExecutable() { + return executable; + } + public ServiceIdentity setHosts(List hosts) { + this.hosts = hosts; + return this; + } + public List getHosts() { + return hosts; + } + public ServiceIdentity setUser(String user) { + this.user = user; + return this; + } + public String getUser() { + return user; + } + public ServiceIdentity setGroup(String group) { + this.group = group; + return this; + } + public String getGroup() { + return group; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != ServiceIdentity.class) { + return false; + } + ServiceIdentity a = (ServiceIdentity) another; + if (name == null ? a.name != null : !name.equals(a.name)) { + return false; + } + if (publicKeys == null ? a.publicKeys != null : !publicKeys.equals(a.publicKeys)) { + return false; + } + if (providerEndpoint == null ? a.providerEndpoint != null : !providerEndpoint.equals(a.providerEndpoint)) { + return false; + } + if (modified == null ? a.modified != null : !modified.equals(a.modified)) { + return false; + } + if (executable == null ? a.executable != null : !executable.equals(a.executable)) { + return false; + } + if (hosts == null ? a.hosts != null : !hosts.equals(a.hosts)) { + return false; + } + if (user == null ? a.user != null : !user.equals(a.user)) { + return false; + } + if (group == null ? a.group != null : !group.equals(a.group)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/ServiceIdentityList.java b/core/zts/src/main/java/com/yahoo/athenz/zts/ServiceIdentityList.java new file mode 100644 index 00000000000..c9c45cf9f74 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/ServiceIdentityList.java @@ -0,0 +1,39 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// ServiceIdentityList - The representation for an enumeration of services in +// the namespace. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class ServiceIdentityList { + public List names; + + public ServiceIdentityList setNames(List names) { + this.names = names; + return this; + } + public List getNames() { + return names; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != ServiceIdentityList.class) { + return false; + } + ServiceIdentityList a = (ServiceIdentityList) another; + if (names == null ? a.names != null : !names.equals(a.names)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/SignedPolicyData.java b/core/zts/src/main/java/com/yahoo/athenz/zts/SignedPolicyData.java new file mode 100644 index 00000000000..9e3b3941da5 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/SignedPolicyData.java @@ -0,0 +1,82 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// SignedPolicyData - A representation of policies object defined in a given +// server. +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class SignedPolicyData { + public PolicyData policyData; + public String zmsSignature; + public String zmsKeyId; + public Timestamp modified; + public Timestamp expires; + + public SignedPolicyData setPolicyData(PolicyData policyData) { + this.policyData = policyData; + return this; + } + public PolicyData getPolicyData() { + return policyData; + } + public SignedPolicyData setZmsSignature(String zmsSignature) { + this.zmsSignature = zmsSignature; + return this; + } + public String getZmsSignature() { + return zmsSignature; + } + public SignedPolicyData setZmsKeyId(String zmsKeyId) { + this.zmsKeyId = zmsKeyId; + return this; + } + public String getZmsKeyId() { + return zmsKeyId; + } + public SignedPolicyData setModified(Timestamp modified) { + this.modified = modified; + return this; + } + public Timestamp getModified() { + return modified; + } + public SignedPolicyData setExpires(Timestamp expires) { + this.expires = expires; + return this; + } + public Timestamp getExpires() { + return expires; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != SignedPolicyData.class) { + return false; + } + SignedPolicyData a = (SignedPolicyData) another; + if (policyData == null ? a.policyData != null : !policyData.equals(a.policyData)) { + return false; + } + if (zmsSignature == null ? a.zmsSignature != null : !zmsSignature.equals(a.zmsSignature)) { + return false; + } + if (zmsKeyId == null ? a.zmsKeyId != null : !zmsKeyId.equals(a.zmsKeyId)) { + return false; + } + if (modified == null ? a.modified != null : !modified.equals(a.modified)) { + return false; + } + if (expires == null ? a.expires != null : !expires.equals(a.expires)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/TenantDomains.java b/core/zts/src/main/java/com/yahoo/athenz/zts/TenantDomains.java new file mode 100644 index 00000000000..fd5bb4f2b58 --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/TenantDomains.java @@ -0,0 +1,38 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// + +package com.yahoo.athenz.zts; +import java.util.List; +import com.yahoo.rdl.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +// +// TenantDomains - +// +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class TenantDomains { + public List tenantDomainNames; + + public TenantDomains setTenantDomainNames(List tenantDomainNames) { + this.tenantDomainNames = tenantDomainNames; + return this; + } + public List getTenantDomainNames() { + return tenantDomainNames; + } + + @Override + public boolean equals(Object another) { + if (this != another) { + if (another == null || another.getClass() != TenantDomains.class) { + return false; + } + TenantDomains a = (TenantDomains) another; + if (tenantDomainNames == null ? a.tenantDomainNames != null : !tenantDomainNames.equals(a.tenantDomainNames)) { + return false; + } + } + return true; + } +} diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java b/core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java new file mode 100644 index 00000000000..9217baaf7fb --- /dev/null +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java @@ -0,0 +1,430 @@ +// +// This file generated by rdl 1.4.8 +// + +package com.yahoo.athenz.zts; +import com.yahoo.rdl.*; + +public class ZTSSchema { + + private final static Schema INSTANCE = build(); + public static Schema instance() { + return INSTANCE; + } + + private static Schema build() { + SchemaBuilder sb = new SchemaBuilder("ZTS"); + sb.version(1); + sb.namespace("com.yahoo.athenz.zts"); + sb.comment("Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. The Authorization Management Service (ZTS) API"); + + sb.stringType("SimpleName") + .comment("Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. Common name types used by several API definitions A simple identifier, an element of compound name.") + .pattern("[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("CompoundName") + .comment("A compound name. Most names in this API are compound names.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("DomainName") + .comment("A domain name is the general qualifier prefix, as its uniqueness is managed.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("EntityName") + .comment("An entity name is a short form of a resource name, including only the domain and entity.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("ServiceName") + .comment("A service name will generally be a unique subdomain.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("LocationName") + .comment("A location name is not yet defined, but will be a dotted name like everything else.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("ActionName") + .comment("An action (operation) name.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("ResourceName") + .comment("A shorthand for a YRN with no service or location. The 'tail' of a YRN, just the domain:entity. Note that the EntityName part is optional, that is, a domain name followed by a colon is valid resource name.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*(:([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?"); + + sb.stringType("YRN") + .comment("A full Yahoo Resource name (YRN).") + .pattern("(yrn:(([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?:(([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?:)?([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*(:([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*)?"); + + sb.stringType("YBase64") + .comment("The Y-specific URL-safe Base64 variant.") + .pattern("[a-zA-Z0-9\\._-]+"); + + sb.stringType("YEncoded") + .comment("YEncoded includes ybase64 chars, as well as = and %. This can represent a user cookie and URL-encoded values.") + .pattern("[a-zA-Z0-9\\._%=-]*"); + + sb.stringType("AuthorityName") + .comment("Used as the prefix in a signed assertion. This uniquely identifies a signing authority.") + .pattern("([a-zA-Z0-9_][a-zA-Z0-9_-]*\\.)*[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + + sb.stringType("SignedToken") + .comment("A signed assertion if identity. i.e. the user cookie value. This token will only make sense to the authority that generated it, so it is beneficial to have something in the value that is cheaply recognized to quickly reject if it belongs to another authority. In addition to the YEncoded set our token includes ; to separate components and , to separate roles") + .pattern("[a-zA-Z0-9\\._%=;,-]*"); + + sb.structType("PublicKeyEntry") + .comment("The representation of the public key in a service identity object.") + .field("key", "String", false, "the public key for the service") + .field("id", "String", false, "the key identifier (version or zone name)"); + + sb.structType("ServiceIdentity") + .comment("The representation of the service identity object.") + .field("name", "ServiceName", false, "the full name of the service, i.e. \"sports.storage\"") + .arrayField("publicKeys", "PublicKeyEntry", true, "array of public keys for key rotation") + .field("providerEndpoint", "String", true, "if present, then this service can provision tenants via this endpoint.") + .field("modified", "Timestamp", true, "the timestamp when this entry was last modified") + .field("executable", "String", true, "the path of the executable that runs the service") + .arrayField("hosts", "String", true, "list of host names that this service can run on") + .field("user", "String", true, "local (unix) user name this service can run as") + .field("group", "String", true, "local (unix) group name this service can run as"); + + sb.structType("ServiceIdentityList") + .comment("The representation for an enumeration of services in the namespace.") + .arrayField("names", "EntityName", false, "list of service names"); + + sb.structType("HostServices") + .comment("The representation for an enumeration of services authorized to run on a specific host.") + .field("host", "String", false, "name of the host") + .arrayField("names", "EntityName", false, "list of service names authorized to run on this host"); + + sb.enumType("AssertionEffect") + .comment("Every assertion can have the effect of ALLOW or DENY.") + .element("ALLOW") + .element("DENY"); + + sb.structType("Assertion") + .comment("A representation for the encapsulation of an action to be performed on a resource by a principal.") + .field("role", "String", false, "the subject of the assertion, a role") + .field("resource", "String", false, "the object of the assertion. Must be in the local namespace. Can contain wildcards") + .field("action", "String", false, "the predicate of the assertion. Can contain wildcards") + .field("effect", "AssertionEffect", false, "the effect of the assertion in the policy language", null) + .field("id", "Int64", true, "assertion id - auto generated by server"); + + sb.structType("Policy") + .comment("The representation for a Policy with set of assertions.") + .field("name", "ResourceName", false, "name of the policy") + .field("modified", "Timestamp", true, "last modification timestamp of this policy") + .arrayField("assertions", "Assertion", false, "list of defined assertions for this policy"); + + sb.structType("PolicyData") + .field("domain", "DomainName", false, "name of the domain") + .arrayField("policies", "Policy", false, "list of policies defined in this server"); + + sb.structType("SignedPolicyData") + .comment("A representation of policies object defined in a given server.") + .field("policyData", "PolicyData", false, "list of policies defined in a domain") + .field("zmsSignature", "String", false, "zms signature generated based on the domain policies object") + .field("zmsKeyId", "String", false, "the identifier of the zms key used to generate the signature") + .field("modified", "Timestamp", false, "when the domain itself was last modified") + .field("expires", "Timestamp", false, "timestamp specifying the expiration time for using this set of policies"); + + sb.structType("DomainSignedPolicyData") + .comment("A signed bulk transfer of policies. The data is signed with server's private key.") + .field("signedPolicyData", "SignedPolicyData", false, "policy data signed by ZMS") + .field("signature", "String", false, "signature generated based on the domain policies object") + .field("keyId", "String", false, "the identifier of the key used to generate the signature"); + + sb.structType("RoleToken") + .comment("A representation of a signed RoleToken") + .field("token", "String", false, "") + .field("expiryTime", "Int64", false, ""); + + sb.structType("Access") + .comment("Access can be checked and returned as this resource.") + .field("granted", "Bool", false, "true (allowed) or false (denied)"); + + sb.structType("RoleAccess") + .arrayField("roles", "EntityName", false, ""); + + sb.structType("TenantDomains") + .arrayField("tenantDomainNames", "DomainName", false, ""); + + sb.structType("Identity") + .comment("Identity - a signed assertion of service or human identity, the response could be either a client certificate or just a regular NToken (depending if the request contained a csr or not).") + .field("name", "CompoundName", false, "name of the identity, fully qualified, i.e. my.domain.service1, or aws.1232321321312.myusername") + .field("certificate", "String", true, "a certificate usable for both client and server in TLS connections") + .field("caCertBundle", "String", true, "the CA certificate chain to use with all IMS-generated certs") + .field("sshServerCert", "String", true, "the SSH server cert, signed by the CA") + .field("serviceToken", "SignedToken", true, "service token instead of TLS certificate") + .mapField("attributes", "String", "String", true, "other config-like attributes determined at boot time"); + + sb.structType("InstanceInformation") + .comment("Instance object that includes requested service details plus host document that is signed by provider as part of the host bootstrap process") + .field("document", "String", false, "signed document containing attributes like IP address, instance-id, account#, etc.") + .field("signature", "String", false, "the signature for the document") + .field("keyId", "String", false, "the keyid used to sign the document") + .field("domain", "CompoundName", false, "the domain of the instance") + .field("service", "SimpleName", false, "the service this instance is supposed to run") + .field("csr", "String", false, "return a certificate in the response"); + + sb.structType("InstanceRefreshRequest") + .comment("InstanceRefreshRequest - a certificate refresh request") + .field("csr", "String", false, "Cert CSR if requesting TLS certificate") + .field("expiryTime", "Int32", true, "in seconds how long token should be valid for"); + + sb.structType("AWSInstanceInformation") + .comment("AWSInstanceInformation - the information a booting EC2 instance must provide to ZTS to authenticate.") + .field("document", "String", false, "signed document containing attributes like IP address, instance-id, account#, etc.") + .field("signature", "String", false, "the signature for the document") + .field("domain", "CompoundName", false, "the domain of the instance") + .field("service", "SimpleName", false, "the service this instance is supposed to run") + .field("csr", "String", false, "return a certificate in the response") + .field("name", "CompoundName", false, "the full service identity name (same as the EC2 instance profile name)") + .field("account", "SimpleName", false, "the account id (as a string) for the instance. parsed from the instance profile ARN") + .field("cloud", "SimpleName", true, "the name of the cloud (namespace) within the account, parsed from the name") + .field("subnet", "SimpleName", false, "the name of the subnet this instance is expected to be running in, parsed from the name") + .field("access", "String", false, "the AWS Access Key Id for the role") + .field("secret", "String", false, "the AWS Secret Access Key for the role") + .field("token", "String", false, "the AWS STS Token for the role") + .field("expires", "Timestamp", false, "the expiration time of the access keys") + .field("modified", "Timestamp", false, "the modified time of the access keys") + .field("flavor", "String", false, "the 'flavor' of the access keys, i.e. \"AWS-HMAC\""); + + sb.structType("AWSCertificateRequest") + .comment("AWSCertificateRequest - a certificate signing request") + .field("csr", "String", false, ""); + + sb.structType("AWSTemporaryCredentials") + .field("accessKeyId", "String", false, "") + .field("secretAccessKey", "String", false, "") + .field("sessionToken", "String", false, "") + .field("expiration", "Timestamp", false, ""); + + sb.enumType("DomainMetricType") + .comment("zpe metric attributes") + .element("ACCESS_ALLOWED") + .element("ACCESS_ALLOWED_DENY") + .element("ACCESS_ALLOWED_DENY_NO_MATCH") + .element("ACCESS_ALLOWED_ALLOW") + .element("ACCESS_ALLOWED_ERROR") + .element("ACCESS_ALLOWED_TOKEN_INVALID") + .element("ACCESS_Allowed_TOKEN_EXPIRED") + .element("ACCESS_ALLOWED_DOMAIN_NOT_FOUND") + .element("ACCESS_ALLOWED_DOMAIN_MISMATCH") + .element("ACCESS_ALLOWED_DOMAIN_EXPIRED") + .element("ACCESS_ALLOWED_DOMAIN_EMPTY") + .element("ACCESS_ALLOWED_TOKEN_CACHE_FAILURE") + .element("ACCESS_ALLOWED_TOKEN_CACHE_NOT_FOUND") + .element("ACCESS_ALLOWED_TOKEN_CACHE_SUCCESS") + .element("ACCESS_ALLOWED_TOKEN_VALIDATE") + .element("LOAD_FILE_FAIL") + .element("LOAD_FILE_GOOD") + .element("LOAD_DOMAIN_GOOD"); + + sb.structType("DomainMetric") + .field("metricType", "DomainMetricType", false, "") + .field("metricVal", "Int32", false, ""); + + sb.structType("DomainMetrics") + .field("domainName", "DomainName", false, "name of the domain the metrics pertain to") + .arrayField("metricList", "DomainMetric", false, "list of the domains metrics"); + + + sb.resource("ServiceIdentity", "GET", "/domain/{domainName}/service/{serviceName}") + .comment("Get info for the specified ServiceIdentity.") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("serviceName", "ServiceName", "name of the service to be retrieved") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("ServiceIdentityList", "GET", "/domain/{domainName}/service") + .comment("Enumerate services provisioned in this domain.") + .pathParam("domainName", "DomainName", "name of the domain") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("PublicKeyEntry", "GET", "/domain/{domainName}/service/{serviceName}/publickey/{keyId}") + .comment("Retrieve the specified public key from the service.") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("serviceName", "SimpleName", "name of the service") + .pathParam("keyId", "String", "the identifier of the public key to be retrieved") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") +; + + sb.resource("HostServices", "GET", "/host/{host}/services") + .comment("Enumerate services provisioned on a specific host") + .pathParam("host", "String", "name of the host") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") +; + + sb.resource("DomainSignedPolicyData", "GET", "/domain/{domainName}/signed_policy_data") + .comment("Get a signed policy enumeration from the service, to transfer to a local store. An ETag is generated for the PolicyList that changes when any item in the list changes. If the If-None-Match header is provided, and it matches the ETag that would be returned, then a NOT_MODIFIED response is returned instead of the list.") + .pathParam("domainName", "DomainName", "name of the domain") + .headerParam("If-None-Match", "matchingTag", "String", null, "Retrieved from the previous request, this timestamp specifies to the server to return any policies modified since this time") + .output("ETag", "tag", "String", "") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") +; + + sb.resource("RoleToken", "GET", "/domain/{domainName}/token") + .comment("Return a security token for the specific role in the namespace that the user can assume. If the role is omitted, then all roles in the namespace that the authenticated user can assume are returned. the caller can specify how long the RoleToken should be valid for by specifying the minExpiryTime and maxExpiryTime parameters. The minExpiryTime specifies that the returned RoleToken must be at least valid (min/lower bound) for specified number of seconds, while maxExpiryTime specifies that the RoleToken must be at most valid (max/upper bound) for specified number of seconds. If both values are the same, the server must return a RoleToken for that many seconds. If no values are specified, the server's default RoleToken Timeout value is used.") + .pathParam("domainName", "DomainName", "name of the domain") + .queryParam("role", "role", "EntityName", null, "only interested for a token for this role") + .queryParam("minExpiryTime", "minExpiryTime", "Int32", null, "in seconds min expiry time") + .queryParam("maxExpiryTime", "maxExpiryTime", "Int32", null, "in seconds max expiry time") + .queryParam("proxyForPrincipal", "proxyForPrincipal", "EntityName", null, "optional this request is proxy for this principal") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("Access", "GET", "/access/domain/{domainName}/role/{roleName}/principal/{principal}") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("roleName", "EntityName", "name of the role to check access for") + .pathParam("principal", "EntityName", "carry out the access check for this principal") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("RoleAccess", "GET", "/access/domain/{domainName}/principal/{principal}") + .pathParam("domainName", "DomainName", "name of the domain") + .pathParam("principal", "EntityName", "carry out the role access lookup for this principal") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("TenantDomains", "GET", "/providerdomain/{providerDomainName}/user/{userName}") + .comment("Get list of tenant domains user has access to for specified provider domain and service") + .pathParam("providerDomainName", "DomainName", "name of the provider domain") + .pathParam("userName", "EntityName", "name of the user to retrieve tenant domain access for") + .queryParam("roleName", "roleName", "EntityName", null, "role name to filter on when looking for the tenants in provider") + .queryParam("serviceName", "serviceName", "ServiceName", null, "service name to filter on when looking for the tenants in provider") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("InstanceInformation", "POST", "/instance") + .comment("Get a cert for service being bootstrapped by supported service") + .input("info", "InstanceInformation", "") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("InstanceRefreshRequest", "POST", "/instance/{domain}/{service}/refresh") + .comment("Refresh self identity if the original identity was issued by ZTS") + .pathParam("domain", "CompoundName", "name of the domain requesting the refresh") + .pathParam("service", "SimpleName", "name of the service requesting the refresh") + .input("req", "InstanceRefreshRequest", "the refresh request") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("AWSInstanceInformation", "POST", "/aws/instance") + .comment("Register an instance in AWS ZTS. Whether this succeeds or not depends on the contents of the request (the request itself is not authenticated or authorized in the normal way). If successful, the Identity is returned as a x.509 client certificate (to be used in TLS operations)") + .input("info", "AWSInstanceInformation", "") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("AWSCertificateRequest", "POST", "/aws/instance/{domain}/{service}/refresh") + .comment("Rotate certs. Make this request with previous cert, the result is new cert for the same identity.") + .pathParam("domain", "CompoundName", "name of the domain requesting the refresh") + .pathParam("service", "SimpleName", "name of the service requesting the refresh") + .input("req", "AWSCertificateRequest", "the refresh request") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("AWSTemporaryCredentials", "GET", "/domain/{domainName}/role/{role}/creds") + .comment("perform an AWS AssumeRole of the target role and return the credentials. ZTS must have been granted the ability to assume the role in IAM, and granted the ability to ASSUME_AWS_ROLE in Athenz for this to succeed.") + .pathParam("domainName", "DomainName", "name of the domain containing the role, which implies the target account") + .pathParam("role", "CompoundName", "the target AWS role name in the domain account, in Athenz terms, i.e. \"the.role\"") + .auth("", "", true) + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + sb.resource("DomainMetrics", "POST", "/metrics/{domainName}") + .comment("called to post multiple zpe related metric attributes") + .pathParam("domainName", "DomainName", "name of the domain the metrics pertain to") + .input("req", "DomainMetrics", "") + .expected("OK") + .exception("BAD_REQUEST", "ResourceError", "") + + .exception("FORBIDDEN", "ResourceError", "") + + .exception("NOT_FOUND", "ResourceError", "") + + .exception("UNAUTHORIZED", "ResourceError", "") +; + + + return sb.build(); + } + +} diff --git a/core/zts/src/main/rdl/AWSAuth.rdli b/core/zts/src/main/rdl/AWSAuth.rdli new file mode 100644 index 00000000000..04e22aeea22 --- /dev/null +++ b/core/zts/src/main/rdl/AWSAuth.rdli @@ -0,0 +1,79 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; +include "Identity.tdl"; + +// AWSInstanceInformation - the information a booting EC2 instance must provide to ZTS to authenticate. +type AWSInstanceInformation Struct { + String document; //signed document containing attributes like IP address, instance-id, account#, etc. + String signature; //the signature for the document + CompoundName domain; //the domain of the instance + SimpleName service; //the service this instance is supposed to run + String csr; //return a certificate in the response + CompoundName name; //the full service identity name (same as the EC2 instance profile name) + SimpleName account; //the account id (as a string) for the instance. parsed from the instance profile ARN + SimpleName cloud (optional); //the name of the cloud (namespace) within the account, parsed from the name + SimpleName subnet; //the name of the subnet this instance is expected to be running in, parsed from the name + String access; //the AWS Access Key Id for the role + String secret; //the AWS Secret Access Key for the role + String token; //the AWS STS Token for the role + Timestamp expires; // the expiration time of the access keys + Timestamp modified; //the modified time of the access keys + String flavor; //the 'flavor' of the access keys, i.e. "AWS-HMAC" +} + +// Register an instance in AWS ZTS. Whether this succeeds or not depends on the +// contents of the request (the request itself is not authenticated or authorized in the normal way). +// If successful, the Identity is returned as a x.509 client certificate (to be used in TLS operations) +resource Identity POST "/aws/instance" { + AWSInstanceInformation info; + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +// AWSCertificateRequest - a certificate signing request +type AWSCertificateRequest Struct { + String csr; +} + +// Rotate certs. Make this request with previous cert, the result +// is new cert for the same identity. +resource Identity POST "/aws/instance/{domain}/{service}/refresh" { + CompoundName domain; //name of the domain requesting the refresh + SimpleName service; //name of the service requesting the refresh + AWSCertificateRequest req; //the refresh request + authenticate; + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +type AWSTemporaryCredentials Struct { + String accessKeyId; + String secretAccessKey; + String sessionToken; + Timestamp expiration; +} + +// perform an AWS AssumeRole of the target role and return the credentials. ZTS +// must have been granted the ability to assume the role in IAM, and granted +// the ability to ASSUME_AWS_ROLE in Athenz for this to succeed. +resource AWSTemporaryCredentials GET "/domain/{domainName}/role/{role}/creds" { + DomainName domainName; //name of the domain containing the role, which implies the target account + CompoundName role; //the target AWS role name in the domain account, in Athenz terms, i.e. "the.role" + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} diff --git a/core/zts/src/main/rdl/DomainMetrics.rdli b/core/zts/src/main/rdl/DomainMetrics.rdli new file mode 100644 index 00000000000..25a22008311 --- /dev/null +++ b/core/zts/src/main/rdl/DomainMetrics.rdli @@ -0,0 +1,50 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; + +// zpe metric attributes +type DomainMetricType Enum { + ACCESS_ALLOWED, + ACCESS_ALLOWED_DENY, + ACCESS_ALLOWED_DENY_NO_MATCH, + ACCESS_ALLOWED_ALLOW, + ACCESS_ALLOWED_ERROR, + ACCESS_ALLOWED_TOKEN_INVALID, + ACCESS_Allowed_TOKEN_EXPIRED, + ACCESS_ALLOWED_DOMAIN_NOT_FOUND, + ACCESS_ALLOWED_DOMAIN_MISMATCH, + ACCESS_ALLOWED_DOMAIN_EXPIRED, + ACCESS_ALLOWED_DOMAIN_EMPTY, + ACCESS_ALLOWED_TOKEN_CACHE_FAILURE, + ACCESS_ALLOWED_TOKEN_CACHE_NOT_FOUND, + ACCESS_ALLOWED_TOKEN_CACHE_SUCCESS, + ACCESS_ALLOWED_TOKEN_VALIDATE, + LOAD_FILE_FAIL, + LOAD_FILE_GOOD, + LOAD_DOMAIN_GOOD +} + +type DomainMetric Struct { + DomainMetricType metricType; + Int32 metricVal; +} + +type DomainMetrics Struct { + DomainName domainName; //name of the domain the metrics pertain to + Array metricList; //list of the domains metrics +} + +// called to post multiple zpe related metric attributes +resource DomainMetrics POST "/metrics/{domainName}" { + DomainName domainName; //name of the domain the metrics pertain to + DomainMetrics req; + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} + diff --git a/core/zts/src/main/rdl/Identity.rdli b/core/zts/src/main/rdl/Identity.rdli new file mode 100644 index 00000000000..db4066f7428 --- /dev/null +++ b/core/zts/src/main/rdl/Identity.rdli @@ -0,0 +1,48 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; +include "Identity.tdl"; + +// Instance object that includes requested service details plus host document +// that is signed by provider as part of the host bootstrap process +type InstanceInformation Struct { + String document; //signed document containing attributes like IP address, instance-id, account#, etc. + String signature; //the signature for the document + String keyId; // the keyid used to sign the document + CompoundName domain; //the domain of the instance + SimpleName service; //the service this instance is supposed to run + String csr; //return a certificate in the response +} + +// Get a cert for service being bootstrapped by supported service +resource Identity POST "/instance" { + InstanceInformation info; + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + } +} + +// InstanceRefreshRequest - a certificate refresh request +type InstanceRefreshRequest Struct { + String csr; // Cert CSR if requesting TLS certificate + Int32 expiryTime (optional); //in seconds how long token should be valid for +} + +// Refresh self identity if the original identity was issued by ZTS +resource Identity POST "/instance/{domain}/{service}/refresh" { + CompoundName domain; //name of the domain requesting the refresh + SimpleName service; //name of the service requesting the refresh + InstanceRefreshRequest req; //the refresh request + authenticate; + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} diff --git a/core/zts/src/main/rdl/Identity.tdl b/core/zts/src/main/rdl/Identity.tdl new file mode 100644 index 00000000000..80bbc15c494 --- /dev/null +++ b/core/zts/src/main/rdl/Identity.tdl @@ -0,0 +1,15 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; + +// Identity - a signed assertion of service or human identity, the response could be either a client certificate +// or just a regular NToken (depending if the request contained a csr or not). +type Identity Struct { + CompoundName name; //name of the identity, fully qualified, i.e. my.domain.service1, or aws.1232321321312.myusername + String certificate (optional); //a certificate usable for both client and server in TLS connections + String caCertBundle (optional); //the CA certificate chain to use with all IMS-generated certs + String sshServerCert (optional); //the SSH server cert, signed by the CA + SignedToken serviceToken (optional); //service token instead of TLS certificate + Map attributes (optional); //other config-like attributes determined at boot time +} diff --git a/core/zts/src/main/rdl/Names.tdl b/core/zts/src/main/rdl/Names.tdl new file mode 100644 index 00000000000..804c0636f3c --- /dev/null +++ b/core/zts/src/main/rdl/Names.tdl @@ -0,0 +1,53 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +// +//Common name types used by several API definitions +// + +//A simple identifier, an element of compound name. +type SimpleName String (pattern="[a-zA-Z0-9_][a-zA-Z0-9_-]*"); + +//A compound name. Most names in this API are compound names. +type CompoundName String (pattern="({SimpleName}\\.)*{SimpleName}"); + +//A domain name is the general qualifier prefix, as its uniqueness is managed. +type DomainName String (pattern="{CompoundName}"); + +//An entity name is a short form of a resource name, including only the domain and entity. +type EntityName String (pattern="{CompoundName}"); + +//A service name will generally be a unique subdomain. +type ServiceName String (pattern="{CompoundName}"); + +//A location name is not yet defined, but will be a dotted name like everything else. +type LocationName String (pattern="{CompoundName}"); + +//An action (operation) name. +type ActionName String (pattern="{CompoundName}"); + +//A shorthand for a YRN with no service or location. The 'tail' of a YRN, just the domain:entity. +//Note that the EntityName part is optional, that is, a domain name followed by a colon is valid resource name. +type ResourceName String (pattern="{DomainName}(:{EntityName})?"); + +//A full Yahoo Resource name (YRN). +type YRN String (pattern="(yrn:({ServiceName})?:({LocationName})?:)?{ResourceName}"); + +//The Y-specific URL-safe Base64 variant. +type YBase64 String (pattern="[a-zA-Z0-9\\._-]+"); + +//YEncoded includes ybase64 chars, as well as = and %. This can represent a user cookie and URL-encoded values. +type YEncoded String (pattern="[a-zA-Z0-9\\._%=-]*"); + +//Used as the prefix in a signed assertion. This uniquely identifies a signing authority. +type AuthorityName String (pattern="{CompoundName}"); + +//A signed assertion if identity. i.e. the user cookie value. +//This token will only make sense to the authority that +//generated it, so it is beneficial to have something in the +//value that is cheaply recognized to quickly reject if +//it belongs to another authority. In addition to the +//YEncoded set our token includes ; to separate components +//and , to separate roles +type SignedToken String (pattern="[a-zA-Z0-9\\._%=;,-]*"); + diff --git a/core/zts/src/main/rdl/Policy.tdl b/core/zts/src/main/rdl/Policy.tdl new file mode 100644 index 00000000000..02d8e989171 --- /dev/null +++ b/core/zts/src/main/rdl/Policy.tdl @@ -0,0 +1,24 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +//Policy Types +include "Names.tdl"; + +//Every assertion can have the effect of ALLOW or DENY. +type AssertionEffect Enum { ALLOW, DENY } + +//A representation for the encapsulation of an action to be performed on a resource by a principal. +type Assertion Struct { + String role; //the subject of the assertion, a role + String resource; //the object of the assertion. Must be in the local namespace. Can contain wildcards + String action; //the predicate of the assertion. Can contain wildcards + AssertionEffect effect (optional, default=ALLOW); //the effect of the assertion in the policy language + Int64 id (optional); //assertion id - auto generated by server +} + +//The representation for a Policy with set of assertions. +type Policy Struct { + ResourceName name; //name of the policy + Timestamp modified (optional); //last modification timestamp of this policy + Array assertions; //list of defined assertions for this policy +} diff --git a/core/zts/src/main/rdl/ServiceIdentity.rdli b/core/zts/src/main/rdl/ServiceIdentity.rdli new file mode 100644 index 00000000000..56a53a36556 --- /dev/null +++ b/core/zts/src/main/rdl/ServiceIdentity.rdli @@ -0,0 +1,75 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; + +//The representation of the public key in a service identity object. +type PublicKeyEntry Struct { + String key; //the public key for the service + String id; //the key identifier (version or zone name) +} + +//The representation of the service identity object. +type ServiceIdentity Struct { + ServiceName name; //the full name of the service, i.e. "sports.storage" + Array publicKeys (optional); //array of public keys for key rotation + String providerEndpoint (optional); //if present, then this service can provision tenants via this endpoint. + Timestamp modified (optional); //the timestamp when this entry was last modified + String executable (optional); //the path of the executable that runs the service + Array hosts (optional); //list of host names that this service can run on + String user (optional); //local (unix) user name this service can run as + String group (optional); //local (unix) group name this service can run as +} + +//Get info for the specified ServiceIdentity. +resource ServiceIdentity GET "/domain/{domainName}/service/{serviceName}" { + DomainName domainName; //name of the domain + ServiceName serviceName; //name of the service to be retrieved + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} + +//The representation for an enumeration of services in the namespace. +type ServiceIdentityList Struct { + Array names; //list of service names +} + +//Enumerate services provisioned in this domain. +resource ServiceIdentityList GET "/domain/{domainName}/service" { + DomainName domainName;//name of the domain + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} + +//Retrieve the specified public key from the service. +resource PublicKeyEntry GET "/domain/{domainName}/service/{serviceName}/publickey/{keyId}" { + DomainName domainName; //name of the domain + SimpleName serviceName; //name of the service + String keyId; //the identifier of the public key to be retrieved + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + } +} + +//The representation for an enumeration of services authorized to run on a specific host. +type HostServices Struct { + String host; //name of the host + Array names; //list of service names authorized to run on this host +} + +//Enumerate services provisioned on a specific host +resource HostServices GET "/host/{host}/services" { + String host; //name of the host + exceptions { + ResourceError BAD_REQUEST; + } +} diff --git a/core/zts/src/main/rdl/SignedPolicies.rdli b/core/zts/src/main/rdl/SignedPolicies.rdli new file mode 100644 index 00000000000..f6df62abb05 --- /dev/null +++ b/core/zts/src/main/rdl/SignedPolicies.rdli @@ -0,0 +1,41 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Policy.tdl"; + +type PolicyData Struct { + DomainName domain; //name of the domain + Array policies; //list of policies defined in this server +} + +//A representation of policies object defined in a given server. +type SignedPolicyData Struct { + PolicyData policyData; //list of policies defined in a domain + String zmsSignature; //zms signature generated based on the domain policies object + String zmsKeyId; //the identifier of the zms key used to generate the signature + Timestamp modified; //when the domain itself was last modified + Timestamp expires; //timestamp specifying the expiration time for using this set of policies +} + +//A signed bulk transfer of policies. The data is signed with server's +//private key. +type DomainSignedPolicyData Struct { + SignedPolicyData signedPolicyData; //policy data signed by ZMS + String signature; //signature generated based on the domain policies object + String keyId; //the identifier of the key used to generate the signature +} + +//Get a signed policy enumeration from the service, to transfer to a local store. +//An ETag is generated for the PolicyList that changes when any item in the list +//changes. If the If-None-Match header is provided, and it matches the ETag that +//would be returned, then a NOT_MODIFIED response is returned instead of the list. +resource DomainSignedPolicyData GET "/domain/{domainName}/signed_policy_data" { + DomainName domainName; //name of the domain + String matchingTag (header="If-None-Match"); //Retrieved from the previous request, this timestamp specifies to the server to return any policies modified since this time + String tag (header="ETag", out); //The current latest modification timestamp is returned in this header + expected OK, NOT_MODIFIED; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + } +} diff --git a/core/zts/src/main/rdl/Tenancy.rdli b/core/zts/src/main/rdl/Tenancy.rdli new file mode 100644 index 00000000000..7556dddc8d5 --- /dev/null +++ b/core/zts/src/main/rdl/Tenancy.rdli @@ -0,0 +1,23 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; + +type TenantDomains Struct { + Array tenantDomainNames; +} + +// Get list of tenant domains user has access to for specified provider domain and service + +resource TenantDomains GET "/providerdomain/{providerDomainName}/user/{userName}?roleName={roleName}&serviceName={serviceName}" { + DomainName providerDomainName; // name of the provider domain + EntityName userName; // name of the user to retrieve tenant domain access for + EntityName roleName (optional); // role name to filter on when looking for the tenants in provider + ServiceName serviceName (optional); // service name to filter on when looking for the tenants in provider + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} diff --git a/core/zts/src/main/rdl/Token.rdli b/core/zts/src/main/rdl/Token.rdli new file mode 100644 index 00000000000..6d5db7fc79f --- /dev/null +++ b/core/zts/src/main/rdl/Token.rdli @@ -0,0 +1,72 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +include "Names.tdl"; + +//A representation of a signed RoleToken +type RoleToken Struct { + String token; + Int64 expiryTime; +} + +//Return a security token for the specific role in the namespace that +//the user can assume. If the role is omitted, then all roles in the +//namespace that the authenticated user can assume are returned. +//the caller can specify how long the RoleToken should be valid for +//by specifying the minExpiryTime and maxExpiryTime parameters. The +//minExpiryTime specifies that the returned RoleToken must be at +//least valid (min/lower bound) for specified number of seconds, +//while maxExpiryTime specifies that the RoleToken must be at most +//valid (max/upper bound) for specified number of seconds. If both +//values are the same, the server must return a RoleToken for that +//many seconds. If no values are specified, the server's default +//RoleToken Timeout value is used. +resource RoleToken GET "/domain/{domainName}/token?role={role}&minExpiryTime={minExpiryTime}&maxExpiryTime={maxExpiryTime}&proxyForPrincipal={proxyForPrincipal}" { + DomainName domainName; //name of the domain + EntityName role (optional); //only interested for a token for this role + Int32 minExpiryTime (optional); //in seconds min expiry time + Int32 maxExpiryTime (optional); //in seconds max expiry time + EntityName proxyForPrincipal (optional); //optional this request is proxy for this principal + authenticate; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError NOT_FOUND; + ResourceError UNAUTHORIZED; + } +} + +//Access can be checked and returned as this resource. +type Access Struct { + Bool granted; //true (allowed) or false (denied) +} + +resource Access GET "/access/domain/{domainName}/role/{roleName}/principal/{principal}" { + DomainName domainName; //name of the domain + EntityName roleName; //name of the role to check access for + EntityName principal; //carry out the access check for this principal + authenticate; + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError FORBIDDEN; + ResourceError UNAUTHORIZED; + ResourceError NOT_FOUND; + } +} + +type RoleAccess Struct { + Array roles; +} + +resource RoleAccess GET "/access/domain/{domainName}/principal/{principal}" { + DomainName domainName; //name of the domain + EntityName principal; //carry out the role access lookup for this principal + authenticate; + expected OK; + exceptions { + ResourceError BAD_REQUEST; + ResourceError UNAUTHORIZED; + ResourceError NOT_FOUND; + } +} diff --git a/core/zts/src/main/rdl/ZTS.rdl b/core/zts/src/main/rdl/ZTS.rdl new file mode 100644 index 00000000000..bb927bef519 --- /dev/null +++ b/core/zts/src/main/rdl/ZTS.rdl @@ -0,0 +1,17 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +// +//The Authorization Management Service (ZTS) API +// +name ZTS; +version 1; +namespace com.yahoo.athenz.zts; + +include "ServiceIdentity.rdli"; +include "SignedPolicies.rdli"; +include "Token.rdli"; +include "Tenancy.rdli"; +include "Identity.rdli"; +include "AWSAuth.rdli"; +include "DomainMetrics.rdli"; diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/AWSCertificateRequestTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/AWSCertificateRequestTest.java new file mode 100644 index 00000000000..068a1fc3062 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/AWSCertificateRequestTest.java @@ -0,0 +1,43 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import org.testng.annotations.Test; + +public class AWSCertificateRequestTest { + + @Test + public void testIdentity() { + AWSCertificateRequest i = new AWSCertificateRequest(); + AWSCertificateRequest i2 = new AWSCertificateRequest(); + + // set + i.setCsr("sample_csr"); + + // getter assertion + assertEquals(i.getCsr(), "sample_csr"); + + assertTrue(i.equals(i)); + assertFalse(i.equals(i2)); + assertFalse(i.equals(new String())); + + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/AWSInstanceInformationTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/AWSInstanceInformationTest.java new file mode 100644 index 00000000000..df4bf1ba282 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/AWSInstanceInformationTest.java @@ -0,0 +1,118 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import org.testng.annotations.Test; + +import com.yahoo.rdl.Timestamp; + +public class AWSInstanceInformationTest { + + @Test + public void testIdentity() { + AWSInstanceInformation i = new AWSInstanceInformation(); + AWSInstanceInformation i2 = new AWSInstanceInformation(); + + // set + i.setDocument("test_doc"); + i.setSignature("test_sig"); + i.setDomain("test.domain"); + i.setService("test.service"); + i.setCsr("test.csr"); + i.setName("test.name"); + i.setAccount("user.test"); + i.setAccess("test.access"); + i.setCloud("test.cloud"); + i.setSubnet("test.subnet"); + i.setSecret("test.secret"); + i.setToken("test.token"); + i.setExpires(Timestamp.fromMillis(123456789123L)); + i.setModified(Timestamp.fromMillis(123456789122L)); + i.setFlavor("test.flavor"); + + i2.setDocument("test_doc"); + i2.setSignature("test_sig"); + i2.setDomain("test.domain"); + i2.setService("test.service"); + i2.setCsr("test.csr"); + i2.setName("test.name"); + i2.setAccount("user.test"); + i2.setAccess("test.access"); + i2.setCloud("test.cloud"); + i2.setSubnet("test.subnet"); + i2.setSecret("test.secret"); + i2.setToken("test.token"); + i2.setExpires(Timestamp.fromMillis(123456789123L)); + i2.setModified(Timestamp.fromMillis(123456789122L)); + + // getter assertion + assertEquals(i.getDocument(), "test_doc"); + assertEquals(i.getSignature(), "test_sig"); + assertEquals(i.getDomain(), "test.domain"); + assertEquals(i.getService(), "test.service"); + assertEquals(i.getCsr(), "test.csr"); + assertEquals(i.getName(), "test.name"); + assertEquals(i.getAccount(), "user.test"); + assertEquals(i.getAccess(), "test.access"); + assertEquals(i.getCloud(), "test.cloud"); + assertEquals(i.getSubnet(), "test.subnet"); + assertEquals(i.getSecret(), "test.secret"); + assertEquals(i.getToken(), "test.token"); + assertEquals(i.getExpires(), Timestamp.fromMillis(123456789123L)); + assertEquals(i.getModified(), Timestamp.fromMillis(123456789122L)); + assertEquals(i.getFlavor(), "test.flavor"); + + assertTrue(i.equals(i)); + + assertFalse(i2.equals(i)); + i2.setModified(null); + assertFalse(i2.equals(i)); + i2.setExpires(null); + assertFalse(i2.equals(i)); + i2.setToken(null); + assertFalse(i2.equals(i)); + i2.setSecret(null); + assertFalse(i2.equals(i)); + i2.setAccess(null); + assertFalse(i2.equals(i)); + i2.setSubnet(null); + assertFalse(i2.equals(i)); + i2.setCloud(null); + assertFalse(i2.equals(i)); + i2.setAccount(null); + assertFalse(i2.equals(i)); + i2.setName(null); + assertFalse(i2.equals(i)); + i2.setCsr(null); + assertFalse(i2.equals(i)); + i2.setService(null); + assertFalse(i2.equals(i)); + i2.setDomain(null); + assertFalse(i2.equals(i)); + i2.setSignature(null); + assertFalse(i2.equals(i)); + i2.setDocument(null); + assertFalse(i2.equals(i)); + + assertFalse(i.equals(new String())); + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/AWSTemporaryCredentialsTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/AWSTemporaryCredentialsTest.java new file mode 100644 index 00000000000..7c453a05646 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/AWSTemporaryCredentialsTest.java @@ -0,0 +1,63 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import org.testng.annotations.Test; + +import com.yahoo.rdl.Timestamp; + +public class AWSTemporaryCredentialsTest { + + @Test + public void testIdentity() { + AWSTemporaryCredentials i = new AWSTemporaryCredentials(); + AWSTemporaryCredentials i2 = new AWSTemporaryCredentials(); + + // set + i.setAccessKeyId("key01"); + i.setSecretAccessKey("test_secret"); + i.setSessionToken("test_token"); + i.setExpiration(Timestamp.fromMillis(123456789123L)); + i2.setAccessKeyId("key01"); + i2.setSecretAccessKey("test_secret"); + i2.setSessionToken("test_token"); + + // getter assertion + assertEquals(i.getAccessKeyId(), "key01"); + assertEquals(i.getSecretAccessKey(), "test_secret"); + assertEquals(i.getSessionToken(), "test_token"); + assertEquals(i.getExpiration(), Timestamp.fromMillis(123456789123L)); + + assertTrue(i.equals(i)); + + assertFalse(i2.equals(i)); + i2.setSessionToken(null); + assertFalse(i2.equals(i)); + i2.setSecretAccessKey(null); + assertFalse(i2.equals(i)); + i2.setAccessKeyId(null); + assertFalse(i2.equals(i)); + + assertFalse(i.equals(new String())); + + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/AccessTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/AccessTest.java new file mode 100644 index 00000000000..a522304f21d --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/AccessTest.java @@ -0,0 +1,52 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import org.testng.annotations.Test; + +public class AccessTest { + + @Test + public void testSetGranted() { + Access a = new Access(); + assertTrue(a.setGranted(true).granted); + } + + @Test + public void testGetGranted() { + Access a = new Access(); + a.setGranted(true); + assertTrue(a.getGranted()); + } + + @Test + public void testEqualsSameObj() { + Access a = new Access().setGranted(true); + Access b = new Access().setGranted(false); + assertTrue(a.equals(a)); + assertFalse(a.equals(b)); + } + + @Test + public void testEqualsDifObj() { + Access a = new Access(); + assertFalse(a.equals(new String())); + } +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/AssertionEffectTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/AssertionEffectTest.java new file mode 100644 index 00000000000..3bda6bac2b6 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/AssertionEffectTest.java @@ -0,0 +1,35 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +public class AssertionEffectTest { + + @Test + public void TestFramStringOK() { + assertEquals(AssertionEffect.ALLOW, AssertionEffect.fromString("ALLOW")); + + } + + @Test(expectedExceptions = { java.lang.IllegalArgumentException.class }) + public void TestFromStringNG() throws Exception { + AssertionEffect.fromString("HOGE"); + } +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/DomainMetricTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/DomainMetricTest.java new file mode 100644 index 00000000000..6a20ff2f502 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/DomainMetricTest.java @@ -0,0 +1,106 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.testng.annotations.Test; + +import junit.framework.Assert; + +public class DomainMetricTest { + + @Test + public void testsetgetMetricType() { + DomainMetric dm = new DomainMetric(); + + dm.setMetricType(DomainMetricType.ACCESS_ALLOWED); + dm.setMetricVal(1); + + Assert.assertEquals(DomainMetricType.ACCESS_ALLOWED, dm.getMetricType()); + Assert.assertEquals(1, dm.getMetricVal()); + Assert.assertEquals(DomainMetricType.ACCESS_ALLOWED, DomainMetricType.fromString("ACCESS_ALLOWED")); + } + + @Test(expectedExceptions = { java.lang.IllegalArgumentException.class }) + public void testMetricTypeException() throws Exception { + DomainMetricType.fromString("NOT_EXIST"); + } + + @Test + public void testMetricTypeEqualsTrueFalse() { + DomainMetric dm1 = new DomainMetric(); + DomainMetric dm2 = new DomainMetric(); + + dm1.setMetricType(DomainMetricType.ACCESS_ALLOWED); + dm1.setMetricVal(1); + dm2.setMetricType(DomainMetricType.ACCESS_ALLOWED); + dm2.setMetricVal(1); + + Assert.assertTrue(dm1.equals(dm2)); + + // change value + dm2.setMetricVal(0); + Assert.assertFalse(dm1.equals(dm2)); + + // change type + dm1.setMetricType(DomainMetricType.ACCESS_ALLOWED_DENY); + Assert.assertFalse(dm1.equals(dm2)); + + Assert.assertFalse(dm1.equals(new String())); + } + + @Test + public void testDomainMetrics() { + DomainMetrics dms1 = new DomainMetrics(); + DomainMetrics dms2 = new DomainMetrics(); + + DomainMetric dm = new DomainMetric(); + + // set/get test + List dmlist = new ArrayList(); + dmlist.add(dm); + + dms1.setDomainName("test.org"); + dms1.setMetricList(dmlist); + dms2.setDomainName("test.org"); + dms2.setMetricList(dmlist); + + assertEquals("test.org", dms1.getDomainName()); + assertEquals(dmlist, dms1.getMetricList()); + + //// equals + // true case + Assert.assertTrue(dms1.equals(dms1)); + assertTrue(dms1.equals(dms2)); + + // false case + dms2.setMetricList(new ArrayList()); + Assert.assertFalse(dms1.equals(dms2)); + + dms2.setDomainName("test.net"); + assertFalse(dms1.equals(dms2)); + + assertFalse(dms1.equals(new String())); + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/DomainSignedPolicyDataTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/DomainSignedPolicyDataTest.java new file mode 100644 index 00000000000..57d9bafdcd7 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/DomainSignedPolicyDataTest.java @@ -0,0 +1,185 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.testng.annotations.Test; + +import com.yahoo.rdl.Timestamp; + +public class DomainSignedPolicyDataTest implements Cloneable { + + @Test + public void testsetgetSignedPolicyData() { + + DomainSignedPolicyData dspd = new DomainSignedPolicyData(); + DomainSignedPolicyData dspd2 = new DomainSignedPolicyData(); + + SignedPolicyData spd = new SignedPolicyData(); + SignedPolicyData spd2 = new SignedPolicyData(); + + PolicyData pd = new PolicyData(); + PolicyData pd2 = new PolicyData(); + + Policy p = new Policy(); + Policy p2 = new Policy(); + + Assertion a = new Assertion(); + Assertion a2 = new Assertion(); + + //// set + // set assertion + a.setRole("user.hoge:role.test"); + a.setResource("user.hoge:coffee.blends.*"); + a.setAction("read"); + a.setEffect(AssertionEffect.ALLOW); + a.setId(0L); + + a2.setRole("user.hoge:role.test"); + a2.setResource("user.hoge:coffee.blends.*"); + a2.setAction("read"); + a2.setEffect(AssertionEffect.ALLOW); + + List al = new ArrayList(); + al.add(a); + + // set policy + p.setName("test_policy"); + p.setModified(Timestamp.fromMillis(1234567890123L)); + p.setAssertions(al); + + p2.setName("test_policy"); + p2.setModified(Timestamp.fromMillis(1234567890123L)); + + List pl = new ArrayList(); + pl.add(p); + + // set policy data + pd.setDomain("user.hoge"); + pd.setPolicies(pl); + + pd2.setDomain("user.hoge"); + + // set signed policy data + spd.setExpires(Timestamp.fromMillis(1234567890123L)); + spd.setModified(Timestamp.fromMillis(1234567890123L)); + spd.setPolicyData(pd); + spd.setZmsKeyId("zmsid"); + spd.setZmsSignature("signature"); + + spd2.setModified(Timestamp.fromMillis(1234567890123L)); + spd2.setPolicyData(pd); + spd2.setZmsKeyId("zmsid"); + spd2.setZmsSignature("signature"); + + // set domain signed policy data + dspd.setKeyId("kid"); + dspd.setSignature("signature"); + dspd.setSignedPolicyData(spd); + + dspd2.setSignature("signature"); + dspd2.setSignedPolicyData(spd); + + //// get assertion + // get assertion + assertEquals(a.getRole(), "user.hoge:role.test"); + assertEquals(a.getResource(), "user.hoge:coffee.blends.*"); + assertEquals((long) a.getId(), 0L); + assertEquals(a.getEffect(), AssertionEffect.ALLOW); + assertEquals(a.getAction(), "read"); + + // get policy + assertEquals(p.getAssertions(), al); + assertEquals(p.getModified(), Timestamp.fromMillis(1234567890123L)); + assertEquals(p.getName(), "test_policy"); + + // get policy data + assertEquals(pd.getDomain(), "user.hoge"); + assertEquals(pd.getPolicies(), pl); + + // get signed policy data + assertEquals(spd.getExpires(), Timestamp.fromMillis(1234567890123L)); + assertEquals(spd.getModified(), Timestamp.fromMillis(1234567890123L)); + assertEquals(spd.getPolicyData(), pd); + assertEquals(spd.getZmsKeyId(), "zmsid"); + assertEquals(spd.getZmsSignature(), "signature"); + + // get domain signed policy data + assertEquals(dspd.getKeyId(), "kid"); + assertEquals(dspd.getSignature(), "signature"); + assertEquals(dspd.getSignedPolicyData(), spd); + + // equals true + assertTrue(a.equals(a)); + assertTrue(p.equals(p)); + assertTrue(pd.equals(pd)); + assertTrue(spd.equals(spd)); + assertTrue(dspd.equals(dspd)); + + // equals false + assertFalse(a2.equals(a)); + a2.setEffect(null); + assertFalse(a2.equals(a)); + a2.setAction(null); + assertFalse(a2.equals(a)); + a2.setResource(null); + assertFalse(a2.equals(a)); + a2.setRole(null); + assertFalse(a2.equals(a)); + + + assertFalse(p2.equals(p)); + p2.setModified(null); + assertFalse(p2.equals(p)); + p2.setName(null); + assertFalse(p2.equals(p)); + + assertFalse(pd2.equals(pd)); + pd2.setDomain(null); + assertFalse(pd2.equals(pd)); + + assertFalse(spd2.equals(spd)); + spd2.setModified(null); + assertFalse(spd2.equals(spd)); + spd2.setZmsKeyId(null); + assertFalse(spd2.equals(spd)); + spd2.setZmsSignature(null); + assertFalse(spd2.equals(spd)); + spd2.setPolicyData(null); + assertFalse(spd2.equals(spd)); + + assertFalse(dspd2.equals(dspd)); + dspd2.setSignature(null); + assertFalse(dspd2.equals(dspd)); + dspd2.setSignedPolicyData(null); + assertFalse(dspd2.equals(dspd)); + + assertFalse(a.equals(new String())); + assertFalse(p.equals(new String())); + assertFalse(pd.equals(new String())); + assertFalse(spd.equals(new String())); + assertFalse(dspd.equals(new String())); + + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/HostServicesTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/HostServicesTest.java new file mode 100644 index 00000000000..5a1f74cea0f --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/HostServicesTest.java @@ -0,0 +1,53 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import org.testng.annotations.Test; + +public class HostServicesTest { + + @Test + public void testHostService() { + HostServices hs = new HostServices(); + HostServices hs2 = new HostServices(); + + List nl = new ArrayList(); + nl.add("sample.service1"); + + // set + hs.setNames(nl); + hs.setHost("sample.com"); + hs2.setHost("sample.com"); + + // getter assertion + assertEquals(hs.getHost(), "sample.com"); + assertEquals(hs.getNames(), nl); + assertTrue(hs.equals(hs)); + + assertFalse(hs2.equals(hs)); + hs2.setHost(null); + assertFalse(hs2.equals(hs)); + + assertFalse(hs.equals(new String())); + + } +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/IdentityTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/IdentityTest.java new file mode 100644 index 00000000000..508439e67dd --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/IdentityTest.java @@ -0,0 +1,79 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.*; + +import java.util.HashMap; + +import org.testng.annotations.Test; + +public class IdentityTest { + + @Test + public void testIdentity() { + Identity i = new Identity(); + Identity i2 = new Identity(); + + HashMap attr = new HashMap() { + + private static final long serialVersionUID = 1L; + + { + put("hosts", "sample.athenz.com"); + put("user", "user.test"); + } + }; + + // set + i.setName("sample"); + i.setCertificate("sample_cert"); + i.setCaCertBundle("sample_certbundle"); + i.setSshServerCert("sample_sshcert"); + i.setServiceToken("sample_token"); + i2.setName("sample"); + i2.setCertificate("sample_cert"); + i2.setCaCertBundle("sample_certbundle"); + i2.setSshServerCert("sample_sshcert"); + i2.setServiceToken("sample_token"); + i.setAttributes(attr); + + // getter assertion + assertEquals(i.getName(), "sample"); + assertEquals(i.getCertificate(), "sample_cert"); + assertEquals(i.getCaCertBundle(), "sample_certbundle"); + assertEquals(i.getSshServerCert(), "sample_sshcert"); + assertEquals(i.getServiceToken(), "sample_token"); + assertEquals(i.getAttributes(), attr); + + assertTrue(i.equals(i)); + + assertFalse(i2.equals(i)); + i2.setServiceToken(null); + assertFalse(i2.equals(i)); + i2.setSshServerCert(null); + assertFalse(i2.equals(i)); + i2.setCaCertBundle(null); + assertFalse(i2.equals(i)); + i2.setCertificate(null); + assertFalse(i2.equals(i)); + i2.setName(null); + assertFalse(i2.equals(i)); + + assertFalse(i.equals(new String())); + } +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/InstanceInformationTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/InstanceInformationTest.java new file mode 100644 index 00000000000..f0540bac635 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/InstanceInformationTest.java @@ -0,0 +1,65 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import org.testng.annotations.Test; + +public class InstanceInformationTest { + + @Test + public void testInstanceInformation() { + InstanceInformation i = new InstanceInformation(); + InstanceInformation i2 = new InstanceInformation(); + + // set + i.setDocument("doc"); + i.setSignature("sig"); + i.setDomain("sample.com"); + i.setService("sample.service"); + i.setCsr("sample_csr"); + i2.setDocument("doc"); + i2.setSignature("sig"); + i2.setDomain("sample.com"); + i2.setService("sample.service"); + + // getter assertion + assertEquals(i.getDocument(), "doc"); + assertEquals(i.getSignature(), "sig"); + assertEquals(i.getDomain(), "sample.com"); + assertEquals(i.getService(), "sample.service"); + assertEquals(i.getCsr(), "sample_csr"); + + assertTrue(i.equals(i)); + + assertFalse(i2.equals(i)); + i2.setService(null); + assertFalse(i2.equals(i)); + i2.setDomain(null); + assertFalse(i2.equals(i)); + i2.setSignature(null); + assertFalse(i2.equals(i)); + i2.setDocument(null); + assertFalse(i2.equals(i)); + + assertFalse(i.equals(new String())); + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/InstanceRefreshRequestTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/InstanceRefreshRequestTest.java new file mode 100644 index 00000000000..ea9d4fa90e3 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/InstanceRefreshRequestTest.java @@ -0,0 +1,47 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import org.testng.annotations.Test; + +public class InstanceRefreshRequestTest { + + @Test + public void testInstanceRefreshRequest() { + InstanceRefreshRequest i = new InstanceRefreshRequest(); + InstanceRefreshRequest i2 = new InstanceRefreshRequest(); + + // set + i.setCsr("test_csr"); + i.setExpiryTime(123456789); + i2.setCsr("test_csr"); + + // getter assertion + assertEquals(i.getCsr(), "test_csr"); + assertEquals(i.getExpiryTime(), (Integer) 123456789); + + assertTrue(i.equals(i)); + assertFalse(i.equals(i2)); + assertFalse(i2.equals(i)); + assertFalse(i.equals(new String())); + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/RoleAccessTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/RoleAccessTest.java new file mode 100644 index 00000000000..38d602a5934 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/RoleAccessTest.java @@ -0,0 +1,53 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import java.util.Arrays; +import java.util.List; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class RoleAccessTest { + + @Test + public void TestsetgetRoleAccess() { + + List roles = Arrays.asList("role1", "role2", "role3"); + RoleAccess ra = new RoleAccess().setRoles(roles); + + Assert.assertEquals(ra.getRoles(), roles); + } + + @Test + public void TestEqualsTrue() { + RoleAccess ra = new RoleAccess(); + ra.equals(ra); + } + + @Test + public void TestEqualsFalse() { + RoleAccess ra1 = new RoleAccess(); + RoleAccess ra2 = new RoleAccess(); + + List roles = Arrays.asList("role1", "role2", "role3"); + ra1.setRoles(roles); + Assert.assertFalse(ra1.equals(ra2)); + Assert.assertFalse(ra1.equals(new String())); + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/RoleTokenTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/RoleTokenTest.java new file mode 100644 index 00000000000..b87c640f650 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/RoleTokenTest.java @@ -0,0 +1,47 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import org.testng.annotations.Test; + +public class RoleTokenTest { + + @Test + public void testRoleToken() { + RoleToken rt = new RoleToken(); + RoleToken rt2 = new RoleToken(); + + // set + rt.setToken("sample_token").setExpiryTime(30L); + rt2.setToken("sample_token").setExpiryTime(40L); + + // getter assertion + assertEquals(rt.getToken(), "sample_token"); + assertEquals(rt.getExpiryTime(), 30L); + assertTrue(rt.equals(rt)); + assertFalse(rt.equals(rt2)); + rt2.setToken(null); + assertFalse(rt2.equals(rt)); + assertFalse(rt.equals(new String())); + + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/ServiceIdentityListTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/ServiceIdentityListTest.java new file mode 100644 index 00000000000..a9416a4431e --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/ServiceIdentityListTest.java @@ -0,0 +1,46 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.testng.annotations.Test; + +public class ServiceIdentityListTest { + + @Test + public void testServiceIdentityList() { + ServiceIdentityList sil = new ServiceIdentityList(); + ServiceIdentityList sil2 = new ServiceIdentityList(); + + List nl = new ArrayList(); + nl.add("sample.service1"); + + sil.setNames(nl); + assertEquals(sil.getNames(), nl); + + assertTrue(sil.equals(sil)); + assertFalse(sil.equals(null)); + assertFalse(sil.equals(sil2)); + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/ServiceIdentityTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/ServiceIdentityTest.java new file mode 100644 index 00000000000..847eb06c49b --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/ServiceIdentityTest.java @@ -0,0 +1,112 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.testng.annotations.Test; + +import com.yahoo.rdl.Timestamp; + +public class ServiceIdentityTest { + + @Test + public void testsetgetServiceIdentity() { + ServiceIdentity si = new ServiceIdentity(); + ServiceIdentity si2 = new ServiceIdentity(); + PublicKeyEntry pkey = new PublicKeyEntry(); + + // set + pkey.setId("key01"); + pkey.setKey("pkey=="); + + List pkeylist = new ArrayList(); + pkeylist.add(pkey); + + List hosts = new ArrayList(); + hosts.add("example.host"); + + si.setExecutable("add-domain"); + si.setGroup("sample_group"); + si.setHosts(hosts); + si.setModified(Timestamp.fromMillis(1234567890123L)); + si.setName("apicomponent"); + si.setProviderEndpoint("sample_endpoint"); + si.setPublicKeys(pkeylist); + si.setUser("user.test"); + + si2.setExecutable("add-domain"); + si.setGroup("sample_group"); + si2.setHosts(hosts); + si2.setModified(Timestamp.fromMillis(1234567890123L)); + si2.setName("apicomponent"); + si2.setProviderEndpoint("sample_endpoint"); + si2.setPublicKeys(pkeylist); + si2.setUser("user.test"); + + // get assertions + assertEquals(pkey.getId(), "key01"); + assertEquals(pkey.getKey(), "pkey=="); + + assertEquals(si.getExecutable(), "add-domain"); + assertEquals(si.getGroup(), "sample_group"); + assertEquals(si.getHosts(), hosts); + assertEquals(si.getModified(), Timestamp.fromMillis(1234567890123L)); + assertEquals(si.getName(), "apicomponent"); + assertEquals(si.getProviderEndpoint(), "sample_endpoint"); + assertEquals(si.getPublicKeys(), pkeylist); + assertEquals(si.getUser(), "user.test"); + + // equals true + assertTrue(pkey.equals(pkey)); + assertTrue(si.equals(si)); + + // equals false + assertFalse(pkey.equals(new String())); + assertFalse(pkey.equals(new PublicKeyEntry())); + PublicKeyEntry pkey2 = new PublicKeyEntry().setKey("pkey=="); + assertFalse(pkey2.equals(pkey)); + + assertFalse(si.equals(new String())); + + si2.setGroup(null); + assertFalse(si2.equals(si)); + si2.setUser(null); + assertFalse(si2.equals(si)); + si2.setHosts(null); + assertFalse(si2.equals(si)); + si2.setExecutable(null); + assertFalse(si2.equals(si)); + si2.setModified(null); + assertFalse(si2.equals(si)); + si2.setProviderEndpoint(null); + assertFalse(si2.equals(si)); + si2.setPublicKeys(null); + assertFalse(si2.equals(si)); + si2.setName(null); + assertFalse(si2.equals(si)); + assertFalse(si2.equals(null)); + assertFalse(si.equals(new ServiceIdentity())); + + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/TenantDomainsTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/TenantDomainsTest.java new file mode 100644 index 00000000000..eed7fd529a5 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/TenantDomainsTest.java @@ -0,0 +1,56 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class TenantDomainsTest { + + @Test + public void TestsetgetTenantDomainNames() { + TenantDomains td = new TenantDomains(); + + List list = new ArrayList(Arrays.asList("A", "B", "C")); + + td.setTenantDomainNames(list); + + Assert.assertEquals(td.getTenantDomainNames(), list); + } + + @Test + public void TestEqualsTrue() { + TenantDomains td = new TenantDomains(); + td.equals(td); + } + + @Test + public void TestEqualsFalse() { + TenantDomains td1 = new TenantDomains(); + TenantDomains td2 = new TenantDomains(); + + td1.setTenantDomainNames(new ArrayList(Arrays.asList("A", "B", "C"))); + + Assert.assertFalse(td1.equals(td2)); + Assert.assertFalse(td1.equals(new String())); + } + +} diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/ZTSCoreTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/ZTSCoreTest.java new file mode 100644 index 00000000000..c24c0fe9809 --- /dev/null +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/ZTSCoreTest.java @@ -0,0 +1,89 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import org.testng.annotations.Test; + +import com.yahoo.rdl.Schema; +import com.yahoo.rdl.Validator; +import com.yahoo.rdl.Validator.Result; + +public class ZTSCoreTest { + + @Test + public void test() { + Schema schema = ZTSSchema.instance(); + assertNotNull(schema); + } + + @Test + public void testYRN() { + String [] goodYRNs = { + "domain:role.test1_", + "domain:role._test1_", + "domain:role._-test1_", + "domain:role._-----", + "domain:role._____", + "3com:role.3role_-", + "3com:entity", + "_domain:3entity_", + "domain:entity", + "my.domain:entity", + "my.domain:entity.path", + "yrn:::domain:entity", + "yrn:::my.domain:my.entity", + "yrn:service::domain:entity", + "yrn:my.service::domain:entity", + "yrn:my.service::my.domain:my.entity", + "yrn:service:location:domain:entity", + "yrn:some.service:some.location:my.domain:my.entity" + }; + + Schema schema = ZTSSchema.instance(); + Validator validator = new Validator(schema); + + System.out.println("Testing valid YRNs..."); + for (String s : goodYRNs) { + System.out.println(s); + Result result = validator.validate(s, "YRN"); + assertTrue(result.valid); + } + + String [] badYRNs = { + "domain:role.-----", + "-domain:role.role1", + "Non_ascii:��", + "cannot-start-with:-dash", + "cannot-use:Punctuation_except_underbar!", + "yrn::location_only", + "yrn:service:location_only", + "non_yrn_prefix:service:location:domain:entity", + "missing_yrn_prefix_service:location:domain:entity" + }; + + System.out.println("Testing bad YRNs..."); + for (String s : badYRNs) { + System.out.println(s); + Result result = validator.validate(s, "YRN"); + assertFalse(result.valid); + } + } +} diff --git a/docs/auth_flow.md b/docs/auth_flow.md new file mode 100644 index 00000000000..e018ff47468 --- /dev/null +++ b/docs/auth_flow.md @@ -0,0 +1,248 @@ +# Architecture - Authorization Flow +---------------------------------------- + +* [Use Cases](#use-cases) + * [Manage Access Control / Permissions for Resources](#manage-access-control-and-permissions-for-resources) + * [Real-Time Service Security](#real-time-service-security) +* [Authorization Flow](#authorization-flow) + * [Centralized Access Control (Control-Plane)](#centralized-access-control-control-plane) + * [Principals](#principals) + * [Unix-Authenticated User](#unix-authenticated-user) + * [Authenticated Service](#authenticated-service) + * [Decentralized Access Control (Data-Plane)](#decentralized-access-control-data-plane) + * [Policy Engine](#policy-engine) +* [Reserved Domains](#reserved-domains) +* [Management Scenarios](#management-scenarios) + * [Simple Centralized Scenario](#simple-centralized-scenario) + * [Multi-tenancy Scenario](#multi-tenancy-scenario) + * [Service Authentication](#service-authentication) + + +## Use Cases +------------ + +### Manage Access Control and Permissions for Resources +----------------------------------------------------- + +Athenz allows you to manage access control and permissions to resources +through the centralized authori**z**ation **m**anagement **s**ervice +(ZMS) and domain configuration that defines resources, roles, and +actions. + +The diagram below shows a simplification of the use case without domain +configuration. + +![Use Case for User Authorization](images/use_case-user_auth.png) + +Your domain configuration file gives you fine control over who can +access resources and what actions can be taken. ZMS manages your domain +files and provides a RESTful API that allows you to create and modify +domain configurations. + +This gives you the ability to control **user access**, +**tenancy**, and secure **editorial content** and **infrastructure +assets**. + +### Real-Time Service Security +------------------------------ + +Athenz provides endpoint protection for services, so a service such as +Sports in the diagram below doesn't need to set network ACLs for other +services wanting to access data. Instead, services are authorized by +Athenz to access resources from another service based on the permissions +defined by the domain administrator. + +![Use Case for Service Authorization](images/use_case-service_auth.png) + +## Authorization Flow +--------------------- + +### Centralized Access Control (Control-Plane) +---------------------------------------------- + +A traditional centralized mechanism works as expected for services that +are not dealing with the decentralized authorization: the server with +resources can simply ask the ZMS directly about access, passing the +NToken and resource/action information to get a simple boolean answer. +This does not scale well enough for data-plane access, since a central +service must be consulted, but requires no local installation of the ZPE +and related storage and synchronization logic, so it is suitable for +human interaction and control-plane provisioning uses. + +#### Principals +--------------- + +In Athenz, actors that can assume a role are called principals. +Principals can be users or services, and users can be those looking for +resources from a service or use the ZMS management console. In the +following sections, we'll look at centralized authorization for the +principals we just discussed. + +##### Unix-Authenticated User +----------------------------- + +In the simplest case, we have no service management at all. When a user, +authenticated with his/her unix credentials, is to be authorized to access +a resource in a service, the user's NToken is passed straight to the target +service. That service must then make a call to ZMS to perform the check, +passing this identity on to ZMS. + +The figure below shows how the user's tool fetches the user's token from +ZMS and then uses it to access a resource. + +![Centralized Authorization for Users](images/centralized_authz_for_users.png) + +When implementing such solution, one must consider all security implications +of sending a user's NToken to another service. Athenz system already provides +some protection from user NToken reuse: +* To request RoleTokens for a given User NToken, the IP address of the + incoming connection must match to the IP address that's in the NToken. +* To make modifications to a domain (e.g. the user is a domain admin) + the IP address of the incoming connection must match to the IP address + that's in the NToken. + +However, if there are multiple services that are supporting centralized +authorization for user, then each one must make sure that the NToken +identifying the user is coming from the same IP address that is registered +in the NToken. Otherwise, if the service1 is compromised, then the user's +NToken can be used to access the resources authorized to that user from +service2 without user's knowledge. + +##### Authenticated Service +--------------------------- + +Similarly, another service could be the principal (instead of a user or +domain manager), in which case it would present its NToken to the target +service, which would perform an identical check with ZMS to confirm +access. + +![Authenticated Service as Principal](images/centralized_authz_for_services.png) + +This model is more desirable that the Authenticated User model since +the domain administrator can create a separate service that only has +access for the given provider, thus provider having access to the +service's identity will not have access to any other resource. + +### Decentralized Access Control (Data-Plane) +--------------------------------------------- + +A more interesting scenario introduces the local policy engine (ZPE), +and a few supporting changes. Rather than directly asking for an access +check with a principal identity, the identity is instead used to get a +ZToken, and that is presented to the target service until it expires. +This mechanism allows a service to make a completely local access check +against ZPE, given a ZToken and locally cached policies. + +Specifically, as shown in the diagram below, the client service/user +presents an authentication token (NToken) from SIA Provider to get an +authorization token (ZToken) from ZTS, and then presents the ZToken +to a target service to access its resources. + +![Decentralized Authorization for Services](images/decentralized_authz_for_services.png) + +That service can make use of a local ZPE to validate the role assertions +in the ZToken, and then correlate them to the asynchronously updated +Policies for the domain involved. The ZPE can make the decision locally, +without going off box. It has no knowledge about specific users, only +roles, and because roles and policies are always in the domain, the +amount of data to distribute to the machine is small. + +#### Policy Engine +------------------ + +The Policy Engine (ZPE) only needs to deal with policies and their roles, +actions, and resources. Concepts like groups would only affect how a +principal can assume a role, so is involved in getting the ZToken, but not +using it in the ZPE. When evaluating policies, the relevant assertions of +all relevant policies must be considered. Assertions are treated like a +big "OR" with the default effect of "Allow", whereas any single "Deny" will +override any other assertions. The policies can have prefix-matching wildcards, +so that glob-style matching needs to be part of the "is this assertion relevant" +logic. + +The policies must be fetched from ZTS for the ZPE to operate. Additionally, the +policies must be refreshed as they change. It is expected that policies +will change at a much lower frequency than user/role assignments, so a daily +pull may be fine for this. + +The policies are not sensitive (secret) information, but their integrity is +important, so the policies fetched (per domain) is signed by both ZMS and ZTS +servers. This requires a predictable serialization of the policy data structures. +That will be fields in the order the schema specifies, serialized to JSON, then +signed. + +The actual fetch is performed by a locally running distribution agent, which could +be implemented as a cron job or other async fetching mechanism. The resulting policies +could be atomically changed per domain. Because the ZPE only will have policies for +the domains of services running on the same host/container, there will be relatively +few of them. + +The API for getting signed policies actually gets many policies in a single list, +which is signed as a whole according to the ordering rules above. + +## Reserved Domains +------------------- + +The following domains are reserved and are automatically provisioned during +server startup: + +* user - The reserved domain for authenticated users. +* sys - The reserved domain for managing system activities +* sys.auth - The Athenz domain itself, which is where policies governing + the top level domains reside. + +## Management Scenarios +----------------------- + +### Simple Centralized Scenario +------------------------------- + +In the simplest scenario, one would perform the following actions. Note that +even in this case, the management calls themselves are protected by Athenz, +so for example one property admin cannot modify another property's domain. + +1. Set up domains as a super admin +2. Assign domain administrators for the domain +3. Create services in the domain +4. Create roles, as the domain admin, to model the expected activity. Assign + users and services as needed. +5. Create policies, with references to resources that are the target of various actions +6. The services would implement the access check calls to enforce access + based on the ZMS "access check" API. This may happen in a container or filter. + +### Multi-tenancy Scenario +-------------------------- + +Setting up a tenant in a provider requires the concepts of a cross-domain +"trust relation". Such a trust relation can be implemented as follows: + +1. A Policy is set up in the group-defining domain (Domain-A) that asserts + that one of its local Roles can “assume” a role in the target Domain. + The role used in this policy is arbitrary, under the control of Domain-A, + who can add/remove users from it as it needs. +2. A Role is created in the target Domain-B, but instead of specifying users, + the trusted domain is set to Domain-A. +3. Domain-B can then set up whatever policies it needs to protect relevant + resources, by referring to its special role as the principal +4. At access check time, when the policy is encountered that indicates a role + that is trusted, that role (in another Domain) must then be consulted to + determine who can assume the role. + +### Service Authentication +-------------------------- + +Authenticating a service involves first registering the service with some +evidence that allow verification of a signed NToken originating from the +service. + +A simple scenario involves a service that is externally managed. Such a service +must arrange on its own to generate a keypair, and securely provide the private +key to the service. In such a case, the external management system must register +the service with its public key in the service management system. The result of +this will be a signing certificate. At that point, we have a public key to verify +the signature on any NTokens coming from the service. + +The NToken expires periodically, so the external service must repeatedly refresh it. +This requires careful handling and rotation of the keypairs, and while simple in +concept, every service would need to implement such a system. + diff --git a/docs/data_model.md b/docs/data_model.md new file mode 100644 index 00000000000..5cce3df56cc --- /dev/null +++ b/docs/data_model.md @@ -0,0 +1,290 @@ +# Architecture - Data Model +------------------------------- + +* [Concepts](#concepts) +* [Data Model](#data-model) + * [Terminology](#terminology) + * [Domains](#domains) + * [Resources](#resources) + * [Policies](#policies) + * [Roles](#roles) + * [Principals](#principals) + * [Users](#users) + * [Services](#services) +* [Tokens](#tokens) + * [Principal Token - NToken](#principal-token-ntoken) + * [Role Token - ZToken](#role-token-ztoken) + +Having a firm grasp on some fundamental concepts will help you +understand the Athenz architecture, the flow for both centralized and +decentralized authorization, and how to set up role-based authorization. + +## Concepts +----------- + +1. Role based Access Control (RBAC) system +2. Symbolic names are used to identify the entities involved in authorization, + so humans can read and understand the assertions +3. Namespaces are strictly partitioned into Domains, which imply an ownership + and control aspect to the names +4. Enforcement can be factored such that it can be done in a distributed way, + making possible data-plane checks that do not go off-box +5. Administrative tasks can be delegated to created subdomains to avoid reliance + on central "super user" administrative roles. +6. Multi-tenancy relationships across domains can also take advantage of delegated + administration +7. Cryptographic signatures are used to ensure the fidelity of assertions and + their lifecycle attributes +8. Policy orientation, as opposed to Resource orientation, allows few assertions + (in policies) to affect a potentially large number of resources, and also allows + more flexible scoping via wildcarding over groups of resources. +9. In Athenz, the authorization management server converts all incoming + data to lowercase before any processing - this applies to all data types + within Athenz (e.g. domain names, role names, policy names, resource + group values, etc). + + +## Data Model +------------- + +![Data Model](images/data_model.png) + +### Terminology +--------------- + +* Domain - a partition/namespace/account of control and isolation +* Role - an entity that takes action on resources, when assumed by a principal +* Resource - an entity to take action on, referred to by unique name +* Policy - a set of assertions that govern usage of resources +* Assertion - a quadruple of <effect, role, action, resource> +* Principal - an authenticated user or service that assumes one or more roles +* User - a user identity that is authenticate by a particular authority like Bouncer +* Service - An identity that exposes reusable resources, authenticated by + an Athenz-aware container, reachable via an explicit endpoint. +* Provider - a type of service that participates in the multi-tenant provisioning protocol. +* Tenant - a Domain that is provisioned to access some Resources in a Provider +* Group - A group is a Role with explicit principal assignments +* Control Plane - operations done to provision or otherwise setup a system, + outside its normal operation. +* Data Plane - operations done in the normal usage of a system, after it is + set up. These operations are usually highly performance sensitive + +There are numerous domains, and domains can themselves “contain” subdomains, +from a creation/deletion perspective. However, all domains and subdomains, +regardless of their administrative relationships, are independent and share +no state. Relationships between them (i.e. quotas, billing roll ups, etc) +must be explicitly introduced if desired. Roles, Policies and their Assertions, +and Services are all defined in a Domain. + +### Domains +----------- + +Domains are namespaces, strictly partitioned, providing a context for +authoritative statements to be made about entities it contains. Only +system administrators can create and destroy top level domains. Each +such domain is assigned users in an administrative role. Those admins +can, in turn, create and delete subdomains, that is, domains that start +with the parent domain as a prefix, using a '.' as a domain delimiter. +For example, "media" is a top level domain, and "media.news" is a +subdomain of it. It is important to note that the only relation between +these two domains involves creation and destruction of the domains -- +the two domains share no state by default, and there is no inheritance +or other relation between them other than that implied by their names. +This allows all domains (whether top level or subdomains) to be +completely partitioned from each other, and the ownership of the +entities defined within a domain is clear. + +A domain is a place to group your resources and entities. + +- All resources are partitioned across some set of domains +- Domains are given a symbolic name, so humans can easily understand a + reference to them +- The domain name is used as a namespace for resource names +- Domains are similar the concept of an account, and provides a + “walled garden” in which to operate + +### Resources +------------- + +Resources aren't explicitly modeled in Athenz, they are referred to by +name. A resource is something that is "owned" and controlled in a +specific domain. + +- Concrete example: a machine +- Abstract example: a security policy +- Identified by a canonical name, so humans can comprehend references + to it + +When referring to resources for authorization, a global naming system +needs to be introduced to handle names in namespaces. A Resource +Name is defined as follows: + + {Domain}:{Entity} + +The Domain is the namespace mentioned above, and the Entity is an entity +path (delimited by periods) within that namespace. The two are often +used together to form a short resource name. For example, if the +media.news property provisions a table in a db service, the resource +name for the table may look like this: + + media.news:storage.db.table + +### Policies +------------ + +To implement access control, we have policies in our domain that govern +the use of our resources. A policy is a set of assertions (rules) about +granting or denying an operation/action on a resource. For example, the +assertion `[grant, update, dev, storage.db.table]` would grant the +**update** operation/action to all the members in the **dev** role on +the **storage.db.table** resource. Note that the assertion fields are +declared as general strings, as they may contain "globbing" style +wildcards, for example "media.news:role.*", which would apply to any +entity that matched the pattern. + +### Roles +--------- + +A role can be thought of as a group, anyone in the group can *assume* +the role that takes a particular action. Every policy assertion +describes what can be done by a role. + +A role can also delegate the determination of membership to another +trusted domain. For example, a netops role managed outside a property +domain. This is how we can model tenant relations between a provider +domain and tenant domains. + +Because roles are defined in domains, they can be partitioned by domain, +unlike users, which are global. This allows the distributed operation to +be more easily scaled. + +### Principals +-------------- + +The actors in Athenz that can assume a role are called principals. These +principals are authenticated and can be users (for example, authenticated by +their Unix or Kerberos credentials). Principals can also be services that +are authenticated by a service management system. Athenz currently provides +service identity and authentication support. In either case, the identity +is expressed as a resource name, and the proof of that identity is provided +as a token, typically called an NToken (aka PrincipalToken). + +#### Users +---------- + +Users are actually defined in some external authority, e.g. Unix or Kerberos +system. A special domain is reserved for the purpose of namespacing users, +the name of that domain is **user**, so some example users are: + +* user.john +* user.doe + +The credentials that the external system requires are exchanged for a NToken +before operating on any data. + +#### Services +------------- + +The concept of a Service Identity is introduced as the identity of independent +agents of execution. Services are defined as a subdomain of the managing domain, +which provides a simple way of naming them. For example: + +* media.news.msbe +* sports.storage + +The relationship of a service identity and its parent domain essentially means +that the parent domain is an authority for the service itself, from a management +perspective. + +A Service Identity may be used as a principal when specifying Roles, just like a +User. Athenz provides support for registering such a Service Identity, in a domain, +along with its public key that can be used to later verify an NToken that is +presented by the service. + +## Tokens +-------- + +Athenz authorization system utilizes 2 types of tokens: Principal Tokens +(NTokens) and RoleTokens (ZTokens). + +### Principal Token (NToken) +---------------------------- + +The Principal token can be thought of an identity token because it +identifies either a user or a service. Users submit their credentials +to ZMS, which validates those credentials and generates, signs and +returns principal tokens. A service generates its principal token using +the service's private key. The principal token can then be used to access +role tokens. + +A principal token is serialized as a string with following attributes: + +- version (v) - the version of the token - U1 for user tokens and S1 + for service tokens +- domain (d) - the domain of principal. For users this will always be + "user" +- name (n) - the name of the principal +- host (h) - the FQDN of the host that issued this token +- salt (a) - a salt +- time (t) - the time the token was issued +- expires (e) - the time the token expires +- keyId (k) - the key identifier of the private key that was used to + sign this token +- ip (i) - the IP address where the request to get this token was + initiated from +- authorized-services (b) - the list of services authorized to use + this user token +- signature (s) - the signature of the other items + +The single letter in parentheses is the key in the serialized string, +where semicolons separate key value pairs, and the key and value are +separated by an equals sign. + +For example: + + v=U1;d=user;n=joe;h=host.athenz.org;a=bb4e;t=1442255237;e=1442258837;k=0;i=10.72.42.32;s=Jw8SvYGYrk + +Note that requests that include a principal token should be protected to +keep the token from being intercepted and reused (for the lifetime of +the token). It is strongly recommended to always use HTTPS for such +requests. + +### Role Token (ZToken) +----------------------- + +Role tokens represent an authoritative statement that a given principal +may assume some number of roles in a domain for a limited period of +time. Like NTokens, they are signed to prevent tampering. In a sense, +the ZTS is an authority except that it is an authority +over Roles instead of Users. + +A role token is serialized as a string with following attributes: + +- version (v) - the version of the token - Z1 +- domain (d) - the domain of role token +- roles (r) - a list of comma-separated role names +- principal (p) - the name of the principal (user/service) who + requested this role token +- host (h) - the FQDN of the ZTS host that issued this token +- salt (a) - a salt +- time (t) - the time the token was issued +- expires (e) - the time the token expires +- keyId (k) - the key identifier of the ZTS private key that was + used to sign this token +- ip (i) - the IP address where the request to get this token was + initiated from +- signature (s) - the signature of the other items + +The single letter in parentheses is the key in the serialized string, +where semicolons separate key value pairs, and the key and value are +separated by an equals sign. + +For example: + + v=Z1;d=media.news;r=editor;p=user.joe;h=host.athenz.com;a=bb4e;t=1442255237;e=1442258837;k=0;i=10.72.42.32;s=P43Vp_LQh1" + +Note that requests that include a ZToken should be encrypted to keep the +ZToken from being intercepted and reused (for the lifetime of the +token). It is strongly recommended to always use HTTPS for such +requests. + diff --git a/docs/dev_centralized_access.md b/docs/dev_centralized_access.md new file mode 100644 index 00000000000..c98d242f19c --- /dev/null +++ b/docs/dev_centralized_access.md @@ -0,0 +1,123 @@ +# Developer Guide - Centralized Access Control +---------------------------------------------- + +* [Client - Obtaining NTokens from SIA Provider](#client---obtaining-ntokens-from-sia-provider) +* [Server - Authorization Checks](#server---authorization-checks) + +In the centralized access control model, the service as a principal +requests its NToken from SIA Provider and then presents it to the +target service which would perform an identical check with ZMS to confirm +access. + +![Authenticated Service as Principal](images/centralized_authz_for_services.png) + +The required steps to setup the environment for provider and tenant +services to support centralized access control are as follows: + +* System administrator creates the provider and tenant domains. +* Tenant Domain administrator generates a public/private key pair + and registers a service in its domain. +* Provider Domain administrator creates a role and policy that + grants access to the given role with configured action and resource. +* Provider Domain administrator adds the Tenant Service to the + role to grant access. +* Tenant Domain administrator installs the private key on the host + that will be running the client/tenant service. + +The next two sections describe the code changes that the developers +must make to their services to support authorized access. + +## Client - Obtaining NTokens from SIA Provider +----------------------------------------------- + +First you need to update your Java project `pom.xml` file to indicate +the dependency on the Athenz auth_core Library: + +``` + + com.yahoo.athenz + auth_core + 1.X.Y + +``` + +The domain administrator must have already generated a public/private key pair +for the service and registered public key in Athenz. The private key must be +available on the host where the service will be running. + +``` + // we're going to extract our private key from a given file + + File rsaPrivateKey = new File("/home/athenz/service/rsa_private.key"); + PrivateKey privateKey = Crypto.loadPrivateKey(rsaPrivateKey); + + // setup the key identifier that the corresponding public key + // has been registered in ZMS, and set the timeout to be 1 hour + + String keyId = "v0"; + long tokenTimeout = TimeUnit.SECONDS.convert(1, TimeUnit.HOURS); + + // create our authority and sia provider object + + Authority authority = new PrincipalAuthority(); + SimpleServiceIdentityProvider siaProvider = + new SimpleServiceIdentityProvider(authority, privateKey, keyId, tokenTimeout); + + // generate a principal for our given domain and service + + Principal principal = siaProvider.getIdentity(domainName, serviceName); + + // include the principal.getSignedToken() string as the value for the + // Athenz-Principal-Auth header +``` + +## Server - Authorization Checks +-------------------------------- + +On the server side the we just need to determine the specific requests +action and the resource, extract the NToken string from the +Athenz-Principal-Auth header value and contact ZMS to carry out the +authorization check. + +First you need to update your Java project `pom.xml` file to indicate +the dependency on the Athenz zms java client Library: + +``` + + com.yahoo.athenz + zms_java_client + 1.X.Y + +``` + +Now, the most important part of the rest of the required code is to +determine the resource and action based on the given http request. +Once you have those two values determined, then all that is left +is to extract the NToken and contact ZMS for the authorization +check. + +``` + HttpServletRequest req = (HttpServletRequest)servletRequest; + + // your method of extracting the resource value from the http + // request. It might need to look at just the URI or possibly + // the full body of the request. + + String resource = translateToMyServiceResource(req); + + // your method of extracting an action string from the http + // request. + + String action = translateToMyServiceAction(req); + + // finally extract the ntoken from the header. + + String nToken = req.getHeader(“Athenz-Principal-Auth”); + + // create a zms authorizer and contact ZMS to carry out the check + + ZMSAuthorizer authorizer = new ZMSAuthorizer(serviceDomain); + boolean access = authorizer.access(action, resource, nToken, null); + + + diff --git a/docs/dev_decentralized_access.md b/docs/dev_decentralized_access.md new file mode 100644 index 00000000000..25b773d395f --- /dev/null +++ b/docs/dev_decentralized_access.md @@ -0,0 +1,455 @@ +# Developer guide - Decentralized Access Control +------------------------------------------------------- + +* [Client - Obtaining RoleTokens from ZTS Server](#client---obtaining-roletokens-from-zts-server) + * [Obtaining ServiceIdentityProvider object](#obtaining-serviceidentityprovider-object) + * [ZTS Client Object](#zts-client-object) + * [Obtaining a Role Token](#obtaining-a-role-token) + * [ZTS getRoleToken Error Codes](#zts-getroletoken-error-codes) + * [Token Caching](#token-caching) + * [Token Prefetch Caching](#token-prefetch-caching) +* [Server - Authorization Checks](#server---authorization-checks) + +In the decentralized access control model, the client service/user +presents an authentication token (NToken) from SIA Provider to get an +authorization token (ZToken) from ZTS, and then presents the ZToken +to a target service to access its resources. + +![Decentralized Authorization for Services](images/decentralized_authz_for_services.png) + +The required steps to setup the environment for provider and tenant +services to support centralized access control are as follows: + +* System administrator creates the provider and tenant domains. +* Tenant Domain administrator generates a public/private key pair + and registers a service in its domain. +* Provider Domain administrator creates a role and policy that + grants access to the given role with configured action and resource. +* Provider Domain administrator adds the Tenant Service to the + role to grant access. +* Provider Domain administrator installs Athenz Policy Engine + Updater (ZPU) on the hosts that will be running the provider + service. ZPU must be configured with the provider domain name + and setup to run as a cron job to periodically download the + latest policy files for the server/provider domain. +* Tenant Domain administrator installs the private key on the host + that will be running the client/tenant service. + +The next two sections describe the code changes that the developers +must make to their services to support authorized access. + +## Client - Obtaining RoleTokens from ZTS Server +------------------------------------------------ + +The client must carry out two major steps in order to retrieve a role +token from ZTS Server. First, using the service's name and private key +it needs to generate a ServiceIdentityProvider object which then it +can use to retrieve a role token using ZTS Client Library. + +### Obtaining ServiceIdentityProvider object +-------------------------------------------- + +First you need to update your Java project `pom.xml` file to indicate +the dependency on the Athenz auth_core Library: + +``` + + com.yahoo.athenz + auth_core + 1.X.Y + +``` + +The domain administrator must have already generated a public/private key pair +for the service and registered public key in Athenz. The private key must be +available on the host where the service will be running. + +``` + // we're going to extract our private key from a given file + + File rsaPrivateKey = new File("/home/athenz/service/rsa_private.key"); + PrivateKey privateKey = Crypto.loadPrivateKey(rsaPrivateKey); + + // setup the key identifier that the corresponding public key + // has been registered in ZMS, and set the timeout to be 1 hour + + String keyId = "v0"; + long tokenTimeout = TimeUnit.SECONDS.convert(1, TimeUnit.HOURS); + + // create our authority and sia provider object + + Authority authority = new PrincipalAuthority(); + SimpleServiceIdentityProvider siaProvider = + new SimpleServiceIdentityProvider(authority, privateKey, keyId, tokenTimeout); +``` + +### ZTS Client Object +--------------------- + +First you need to update your Java project `pom.xml` file to indicate +the dependency on the ZTS Java Client Library: + +``` + + com.yahoo.athenz + zts_java_client + 1.X.Y + +``` + +ZTS Client Library provides several constructors; however, the most +commonly used one would be the constructor to create a client object +based on a given domain and service identifier. + +``` + /** + * Constructs a new ZTSClient object with the given service identity + * and media type set to application/json. The url for ZTS Server is + * automatically retrieved from the athenz_config package's configuration + * file (zts_url field). The service's principal token will be retrieved + * from the SIA Provider. + * Default read and connect timeout values are 30000ms (30sec). The application can + * change these values by using the yahoo.zts_java_client.read_timeout and + * yahoo.zts_java_client.connect_timeout system properties. The values specified + * for timeouts must be in milliseconds. + * @param domainName name of the domain + * @param serviceName name of the service + * @param siaProvider ServiceIdentityProvider object for the given service + */ + public ZTSClient(String domainName, String serviceName, ServiceIdentityProvider siaProvider); +``` + +ZTSClient object must be closed to release any allocated resources. ZTSClient +class implements Closeable interface. + +The client library automatically retrieves the URL of the ZTS server +from its configuration file. + +For example, if you have already registered service called `storage` in +the domain `athenz`, use the command below to instantiate a ZTS +Client object: + +``` + SimpleServiceIdentityProvider siaProvider = + new SimpleServiceIdentityProvider(authority, privateKey, keyId, tokenTimeout); + try (ZTSClient ztsClient = new ZTSClient("athenz", "storage", siaProvider)) { + // carry out requests against ZTS Server + } +``` + +If your application will only handle a single service, then rather than +creating and destroying a ZTSClient object for every request, you can +just create a single instance of the ZTSClient and use that instance +by multiple threads to request role tokens: + +``` + // in your service initialization method + + void initZTSClient(String domainName, String serviceName, String privateKeyPath, String keyId) { + + File rsaPrivateKey = new File("/home/athenz/service/rsa_private.key"); + PrivateKey privateKey = Crypto.loadPrivateKey(rsaPrivateKey); + + // set the timeout to be 1 hour + + long tokenTimeout = TimeUnit.SECONDS.convert(1, TimeUnit.HOURS); + + // create our authority and sia provider object + + Authority authority = new PrincipalAuthority(); + SimpleServiceIdentityProvider siaProvider = + new SimpleServiceIdentityProvider(authority, privateKey, keyId, tokenTimeout); + ztsClient = new ZTSClient(domainName, serviceName, siaProvider); + } + + // then in our service processing code you can use the ztsClient + // instance to retrieve role tokens for your requested domain and role + + RoleToken roleToken = ztsClient.getRoleToken(providerDomain, roleName); +``` + +### Obtaining a Role Token +-------------------------- + +To obtain a Role Token, the application would use one the following methods +from the `ZTSClient` class: + +``` + /** + * For the specified requester(user/service) return the corresponding Role Token that + * includes the list of roles that the principal has access to in the specified domain. + * The client will automatically fulfill the request from the cache, if possible. + * The default minimum expiry time is 900 secs (15 mins). + * @param domainName name of the domain + * @return ZTS generated Role Token. ZTSClientException will be thrown in case of failure + */ + public RoleToken getRoleToken(String domainName); + + /** + * For the specified requester(user/service) return the corresponding Role Token that + * includes the specified role name that the principal has access to in the specified + * domain. The client will automatically fulfill the request from the cache, if possible. + * The default minimum expiry time is 900 secs (15 mins). + * @param domainName name of the domain + * @param roleName only interested in roles with this value + * @return ZTS generated Role Token. ZTSClientException will be thrown in case of failure + */ + public RoleToken getRoleToken(String domainName, String roleName); +``` + +In the simplest case, the method only requires the caller to specify the +domain that the application will be accessing. Thus, it needs a Role +Token for that domain. For example, if the `athenz.storage` service +identifier is trying to access a resource from a domain `weather`, then +the API call to retrieve the `roleToken` would be the following: + +``` + RoleToken roleToken = null; + try { + roleToken = ztsClient.getRoleToken("weather"); + } catch (ZTSClientException ex) { + // log error using ex.getCode() and ex.getMessage() + } +``` + +Then the client will include the retrieved Role Token value as one of +its headers when submitting its request to the provider service: + +``` + ztsClient.getHeader(); // returns Header name: "Athenz-Role-Auth" + roleToken.getToken() // returns header value +``` + +However, the above method returns a RoleToken that includes all the roles +the given principal has access to the provider domain. This may not be +desirable as it violates the principle of least privilege. Instead, the +caller should (in fact, the system administrator has the option to require +this rather than making it possible) specify the roleName that it requires +to complete its request.For example, if the `athenz.storage` service +identifier is trying to access a resource from a domain `weather` and requires +only read access which is granted to the service as being member of the `readers` +role in the `weather` domain, then the API call to retrieve the `roleToken` +would be the following: + +``` + RoleToken roleToken = null; + try { + roleToken = ztsClient.getRoleToken("weather", "readers"); + } catch (ZTSClientException ex) { + // log error using ex.getCode() and ex.getMessage() + } +``` + +#### ZTS getRoleToken Error Codes +--------------------------------- + +When communicating with ZTS Server to obtain a RoleToken, the ZTS Server +will return the following 4xx error codes if it's unable to successfully +process the request: + +- **400** The domain name specified in the request to issue a + RoleToken for contains invalid characters. +- **401** The request could not be successfully authenticated. This + usually indicates that either the Service is not properly registered + in ZMS or there is a mismatch between the registered public key and + the private key that was used to generate the ServiceToken. +- **403** The service identity does not have access to any resources + in the specified domain. +- **404** The domain specified in the request to issue a RoleToken for + does not exist. + +### Token Caching +----------------- + +The ZTS Client Library automatically caches any tokens returned by the +ZTS Server, so any subsequent requests for a Role Token for the same +domain are fulfilled from the local cache as opposed to connecting to +the ZTS Server every time. This provides better performance by reusing +the same Role Token because they’re valid for two hours by default. The +client library will only return cached Role Tokens if they’re valid for +at least 15 minutes. If you want to change this default time, the +application can set the following system property before starting their +application: + + athenz.zts.client.token_min_expiry_time + +The value of this property must be specified in seconds. If for any +reason you need to disable caching or have better control how long to +cache tokens, the application can use the full `getRoleToken` API as +shown below: + +``` + /** + * For the specified requester(user/service) return the corresponding + * Role Token that includes the list of roles that the principal has + * access to in the specified domain + * @param domainName name of the domain + * @param roleName (optional) only interested in the specified role + * @param trustDomain (optional) only look for trusted roles in this domain + * @param minExpiryTime (optional) specifies that the returned RoleToken + * must be at least valid (min/lower bound) for specified number + * of seconds, + * @param maxExpiryTime (optional) specifies that the returned RoleToken + * must be at most valid (max/upper bound) for specified number + * of seconds. + * @param ignoreCache ignore the cache and retrieve the token from ZTS Server + * @return ZTS generated Role Token + */ + public RoleToken getRoleToken(String domainName, String roleName, + Integer minExpiryTime, Integer maxExpiryTime, boolean ignoreCache); +``` + +To completely disable caching, the `ignoreCache` argument (the last +boolean argument) in the `getRoleToken` method can be passed `true`. + +For example, the following call disables caching: + +``` + RoleToken roleToken = null; + try { + roleToken = ztsClient.getRoleToken("weather", "readers", null, null, null, true); + } catch (ZTSClientException ex) { + // log error using ex.getCode() and ex.getMessage() + } +``` + +The code above will contact the ZTS Server every time the applications +requests a role token for accessing resources in domain `weather`. + +The other two important arguments for token caching are the +`minExpiryTime` and `maxExpiryTime` arguments. When passed `null`, the +client library uses the default values, which as described above gets +tokens that are valid for two hours and caches them until they’re 15 +minutes from expiration time. + +If your application wants to take advantage of longer caching, then it +can request tokens from the ZTS Server with an expiration time longer +than the default two hours. For example, the requirements for your +server might be that you can use role tokens that must be at least 30 +minutes from expiration and they can be valid for up to four hours. + +For example, the following would set up the API call that requests +tokens from the Weather server that can only be used if they are 30 +minutes from expiration and last four hours: + +``` + RoleToken roleToken = null; + try { + roleToken = ztsClient.getRoleToken("weather", "readers", null, + 30 * 60, 4 * 60 * 60, true); + } catch (ZTSClientException ex) { + // log error using ex.getCode() and ex.getMessage() + } +``` + +With the above request configuration, your client library will only make +a single connection to the ZTS Server to retrieve a new token once every +three hours and 30 minutes. All other requests within that time frame +will be fulfilled from the local cache. + +### Token Prefetch Caching +--------------------------- + +In addition to the caching described above, the cache can be kept +`fresh` automatically by the ZTS Client library. The library detects +when a token will expire within a short period of time and will actively +retrieve a new token to replace it. In this way, the client will always +get a usable token from the cache. With configuration, the prefetch +mechanism can be triggered automatically upon calling any of the +`getRoleToken` API. + +By default the feature is disabled, but can be enabled via a System +Property. Here is an example of enabling automatic prefetch of tokens. + +``` + athenz.zts.client.prefetch_auto_enable=true +``` + +To use the prefetch caching feature with `getRoleToken`, specify the +`ignoreCache` boolean argument with value `false`. + +## Server - Authorization Checks +-------------------------------- + +In the Athenz enabled server the general processing of a client request +goes as follows: + +1. Your service extracts the Role Token from the header + (`Athenz-Role-Auth`) of the incoming request which it will pass it to + the ZPE API. +2. Your service determines the Action and Resource based on the client + request. Then the service calls the ZPE API with the Action, + Resource, and Role Token to determine authorization access. + +Additionally, the system administrator needs to make sure that +ZPU (Athenz ZPE Policy Updater) utility is installed and configured +to run on the server host. ZPU will download the configured domain +policy data that is used by the ZPE library for authorization +checks. + +First, you need to update your Java project `pom.xml` file to indicate +your dependency on the ZPE Java Client Library: + +``` + + com.yahoo.athenz + zpe_java_client + 1.X.Y + +``` + +Now, the most important part of the rest of the required code is to +determine the resource and action based on the given http request. +Once you have those two values determined, then all that is left +is to extract the ZToken and call ZPE method for the authorization +check. + +``` + HttpServletRequest req = (HttpServletRequest)servletRequest; + + // your method of extracting the resource value from the http + // request. It might need to look at just the URI or possibly + // the full body of the request. + + String resource = translateToMyServiceResource(req); + + // your method of extracting an action string from the http + // request. + + String action = translateToMyServiceAction(req); + + // finally extract the ntoken from the header. + + String zToken = req.getHeader(“Athenz-Role-Auth”); + + // this is a thread-safe call + + AccessCheckStatus status = AuthZpeClient.allowAccess(zToken, resource, action); + if (status != AccessCheckStatus.ALLOW) { + // status provides specific enum value for each reason + // why the access check was denied. For example - here are + // some of the possible values that are returned: + // AccessCheckStatus.DENY - specific rule caused the deny effect + // AccessCheckStatus.DENY_NO_MATCH - there was no match to any assertions + // defined in the domain policy file so the default DENY effect was returned + // AccessCheckStatus.DENY_ROLETOKEN_INVALID - The roletoken provided in the + // request was either invalid or expired + throw new Exception(“Access denied”); + } +``` + +If you want to know which role caused the allow or deny a match to be +returned by the API call, you can use the following API: + +``` + StringBuilder matchRoleName = new StringBuilder(256); + AccessCheckStatus status = AuthZpeClient.allowAccess(zToken, resource, action, matchRoleName); + if (status != AccessCheckStatus.ALLOW) { + throw new Exception(“Access denied”); + } +``` + +The variable `matchRoleName` will include the name of the role from the +assertion that matched the given action and resource. + + diff --git a/docs/dev_environment.md b/docs/dev_environment.md new file mode 100644 index 00000000000..4f90703e9b5 --- /dev/null +++ b/docs/dev_environment.md @@ -0,0 +1,58 @@ +# Development Environment +------------------------- + +* [Development Tools](#development-tools) + * [Java Platform JDK 8](#java-platform-jdk-8) + * [Maven](#maven) + * [Git Client](#git-client) + * [Go](#go) +* [Build Steps](#build-steps) + +## Development Tools +-------------------- + +If you would like to build your own copy of Athenz rather than +using the pre-built binary packages, then here is the list of +development tools you need to have installed on your system. + +### Java Platform JDK 8 +----------------------- + +To build Athenz components, you must have Java Platform JDK 8 installed +on your machine. The main authorization services - ZMS and ZTS, are +written in Java and using embedded Jetty. + +[Oracle Java Platform JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) + +Athenz has been developed and tested with Oracle Java Platform JDK 8. +However, it should compile and run without any issues with OpenJDK 8 as well. + +### Maven +--------- + +Download and install [Apache Maven](http://maven.apache.org/download.cgi). + +### Git Client +-------------- + +If you don't have git client installed on your host, you can download +one from [Git website](https://git-scm.com/downloads) + +### Go +------ + +Install go by following the directions at +[Getting Started - The Go Programming Language](https://golang.org/doc/install). + +## Build Steps +-------------- + +To build Athenz components, change to the top level directory where +Athenz code has been checked out and execute: + +``` +git clone https://github.com/yahoo/AtheNZ.git +cd AtheNZ +mvn clean install +``` + diff --git a/docs/images/centralized_authz_for_services.png b/docs/images/centralized_authz_for_services.png new file mode 100644 index 0000000000000000000000000000000000000000..1d55aefb4169e62cd5a377f7d411cc80326aa974 GIT binary patch literal 55521 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 DbtEF? literal 0 HcmV?d00001 diff --git a/docs/images/decentralized_authz_for_services.png b/docs/images/decentralized_authz_for_services.png new file mode 100644 index 0000000000000000000000000000000000000000..4d9435f897490468ae986ad60f206fa640c37cbf GIT binary patch literal 71142 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

  • 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ℜ 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�xom@^EH>t%q;=s{W6;oYAx5rj5JyCKdWi^ zj&PKv#C#w5qcOZWH|nh^?zA2x96s+pB^4ERDW4-T1mS1}B(Oo>;G8KlDBte)rb#cg{C~Z{4S~dWQolp~ zUsHtO^M;7vBu*b)`X|sU{_7a<*WbZn{HtVmY`7$UFPXo;nP`15r;Pi)W{v;(Re_&R zpvCXH)(Z2Uo|gZ3F@}40$pvcD%l!2m{re69-g7Ygd58ad_|aGW6|jXn|Miu5LwA8; zB1fe~BK`L$`{==4VSZ|7*8Z=-BltLjVXoT#RQO+`#1sX0)k&LNNcG>hg%AaX$!*0c h`Co$S9u2hNQlS^mka9o{vY>QuipRw literal 0 HcmV?d00001 diff --git a/docs/images/use_case-service_auth.png b/docs/images/use_case-service_auth.png new file mode 100644 index 0000000000000000000000000000000000000000..7a6e92349ec93205feeb806cf6093600fc94612d GIT binary patch literal 58327 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 D>^%Z= literal 0 HcmV?d00001 diff --git a/docs/images/use_case-user_auth.png b/docs/images/use_case-user_auth.png new file mode 100644 index 0000000000000000000000000000000000000000..0396feb010a690b09f89a681f07ccdea3ad71fcf GIT binary patch literal 65390 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

    >s3|6iq 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 literal 0 HcmV?d00001 diff --git a/docs/reg_service_guide.md b/docs/reg_service_guide.md new file mode 100644 index 00000000000..05e59d89c9e --- /dev/null +++ b/docs/reg_service_guide.md @@ -0,0 +1,82 @@ +# Registering ZMS Service Identity +---------------------------------- + +* [Key Generation](#key-generation) + * [Private Keys](#private-keys) + * [Key Rotation](#key-rotation) +* [Service Identity Registration](#service-identity-registration) + +Once a domain has been registered in Athenz, the administrator will +register service identities that are specified in domain roles and +policy assertions. The latter can reference those roles having access to +specified resources. + +Before you can register the service identity, you'll need to generate +keys. We'll cover how to create the keys and register the service +identity next. + +## Key Generation +---------------- + +The registration process requires the domain administrator to generate a +private/public RSA key pair (recommended to be at least 2048 bit) +for the service. Athenz also supports EC keys. + +The following are the keys and the services that use those keys: + +- **private key** - The service agent uses the private key to generate a + `ServiceToken` identifying the service. +- **public key** - ZMS/ZTS then use the public key to validate the + `ServiceToken` generated by service agent. + +The `openssl` command-line utility is used to generate the key pair: + + $ openssl genrsa -out service_private.pem 2048 + $ openssl rsa -in service_private.pem -pubout > service_public.pem + + +The zms-cli client utility requires that the public key have an extension +of .pem. + +### Private Keys +---------------- + +The private key file must be installed on all hosts where the client +service will be running. + +Each key pair has a key identifier that will be included in the +generated `ServiceTokens (NToken)` as the value of the `k` component. If +the service’s private key has been compromised or the service has a policy +to periodically rotate the keys, the service administrator will generate +a new key pair, remove the public key with the old identifier, and +register a new public key with a different key identifier. Typically, a +service would start with "0" as its first identifier and increment when +required. + +### Key Rotation +---------------- + +If the service’s private key has been compromised or the service has a +policy to periodically rotate the keys, the service administrator will +generate a new key pair, remove the public key with identifier 0 and +register a new public key with a different key identifier as shown +below: + + $ zms-cli -d delete-public-key 0 + $ zms-cli -d add-public-key 1 new_service_public.pem + +As mentioned above, the key identifier is included in generated Service +Tokens as the value of the `k` component. + +## Service Identity Registration +-------------------------------- + +To create a service identity object in ZMS with the generated public key: + + $ zms-cli -d add-service + +For example, to register the service "storage" in the domain ``athenz`` +with the key identifier ``0`` and the public key stored in the file ``storage_public.pem``, +run the following ``zms-cli`` command: + + $ zms-cli -d athenz add-service storage 0 ./storage_public.pem diff --git a/docs/setup_zms.md b/docs/setup_zms.md new file mode 100644 index 00000000000..27cc738d128 --- /dev/null +++ b/docs/setup_zms.md @@ -0,0 +1,93 @@ +# Setup ZMS (AuthoriZation Management System) +--------------------------------------------- + +* [Requirements](#requirements) + * [JDK 8](#jdk-8) +* [Getting Software](#getting-software) +* [Configuration](#configuration) + * [Private/Public Key Pair](#privatepublic-key-pair) + * [Self Signed X509 Certificate](#self-signed-x509-certificate) +* [Start ZMS Server](#start-zms-server) + +## Requirements +--------------- + +The following tools are required to be installed on hosts +configured to run ZMS server. + +### JDK 8 +--------- + +ZMS Server is written in Java and using embedded Jetty. + +[Oracle Java Platform JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) + +While ZMS has been developed and tested with Oracle Java Platform JDK 8 +it should run successfully with OpenJDK 8 as well. + +## Getting Software +------------------- + +Download latest ZMS binary release from + +``` +https://github.com/yahoo/AtheNZ/releases/latest +``` + +```shell +$ tar xvfz athenz-zms-X.Y-bin.tar.gz +$ cd athenz-zms-X.Y +``` + +## Configuration +---------------- + +To run ZMS Server, the system administrator must generate the keys +and make necessary changes to the configuration settings. + +### Private/Public Key Pair +--------------------------- + +Generate a unique private/public key pair that ZMS Server will use +to sign any NTokens it issues. From the `athenz-zms-X.Y` directory +execute the following commands: + +``` +cd var/zms_server/keys +openssl genrsa -out zms_private.pem 2048 +openssl rsa -in zms_private.pem -pubout > zms_public.pem +``` + +### Self Signed X509 Certificate +-------------------------------- + +Generate a self-signed X509 certificate for ZMS Server HTTPS +support. After we generate the X509 certificate, we need to add +that certificate along with its private key to a keystore for Jetty +use. From the `athenz-zms-X.Y` directory execute the following +commands: + +``` +cd var/zms_server/certs +openssl req -x509 -newkey rsa:2048 -keyout zms_key.pem -out zms_cert.pem -days 365 +``` + +Generate a keystore in PKCS#12 format: + +``` +openssl pkcs12 -export -out zms_keystore.pkcs12 -in zms_cert.pem -inkey zms_key.pem +``` + +## Start ZMS Server +------------------- + +Set the required Athenz ROOT environment variable to the `athenz-zms-X.Y` +directory and from there start the ZMS Server by executing: + +``` +export ROOT= +sudo -E bin/zms_start.sh +``` + +Based on the sample configuration file provided, ZMS Server will be listening +on port 4443. diff --git a/docs/setup_zts.md b/docs/setup_zts.md new file mode 100644 index 00000000000..cb53c590f93 --- /dev/null +++ b/docs/setup_zts.md @@ -0,0 +1,130 @@ +# Setup ZTS (authoriZation Token System) +---------------------------------------- + +* [Requirements](#requirements) + * [JDK 8](#jdk-8) +* [Getting Software](#getting-software) +* [Configuration](#configuration) + * [Private/Public Key Pair](#privatepublic-key-pair) + * [Self Signed X509 Certificate](#self-signed-x509-certificate) + * [ZMS Certificate TrustStore](#zms-certificate-truststore) + * [Register ZTS Service](#register-zts-service) + * [Update Athenz Configuration File](#update-athenz-configuration-file) +* [Start ZMS Server](#start-zms-server) + +## Requirements +--------------- + +The following tools are required to be installed on hosts +configured to run ZTS server. + +### JDK 8 +--------- + +ZTS Server is written in Java and using embedded Jetty. + +[Oracle Java Platform JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) + +While ZTS has been developed and tested with Oracle Java Platform JDK 8 +it should run successfully with OpenJDK 8 as well. + +## Getting Software +------------------- + +Download latest ZTS binary release from + +``` +https://github.com/yahoo/AtheNZ/releases/latest +``` + +```shell +$ tar xvfz athenz-zts-X.Y-bin.tar.gz +$ cd athenz-zts-X.Y +``` + +## Configuration +---------------- + +To run ZTS Server, the system administrator must generate the keys +and make necessary changes to the configuration settings. + +### Private/Public Key Pair +--------------------------- + +Generate a unique private/public key pair that ZTS Server will use +to sign any ZTokens it issues. From the `athenz-zts-X.Y` directory +execute the following commands: + +``` +cd var/zts_server/keys +openssl genrsa -out zts_private.pem 2048 +openssl rsa -in zts_private.pem -pubout > zts_public.pem +``` + +### Self Signed X509 Certificate +-------------------------------- + +Generate a self-signed X509 certificate for ZTS Server HTTPS +support. After we generate the X509 certificate, we need to add +that certificate along its private key to a keystore for Jetty +use. From the `athenz-zts-X.Y` directory execute the following +commands: + +``` +cd var/zts_server/certs +openssl req -x509 -newkey rsa:2048 -keyout zts_key.pem -out zts_cert.pem -days 365 +``` + +Generate a keystore in PKCS#12 format: + +``` +openssl pkcs12 -export -out zts_keystore.pkcs12 -in zts_cert.pem -inkey zts_key.pem +``` + +### ZMS Certificate TrustStore +------------------------------ + +ZTS Server needs to access ZMS Server to download all domain details +in order to issue RoleTokens. Since ZMS Server is running with a +self-signed certificate, we need to generate a truststore for the +java http client to use when communicating with the ZMS Server. +From your ZMS Server installation, copy the `zms_cert.pem` file +from the `athenz-zms-X.Y/var/zms_server/certs` directory to the +`athenz-zts-X.Y/var/zts_server/certs` directory and execute the following +command: + +``` +keytool -importcert -noprompt -alias zms -keystore zts_truststore.jks -file zms_cert.pem -storepass athenz +``` + +### Register ZTS Service +------------------------ + +In order for ZTS to access ZMS domain data, it must identify itself +as a registered service in ZMS. Using the `zms-cli` utility, we will +register a new service in `sys.auth` domain: + +``` +cd athenz-zts-X.Y +bin/zms-cli -k -z https://:4443/zms/v1 -d sys.auth add-service zts 0 var/zts_server/keys zts_public.pem +``` + +### Update Athenz Configuration File +------------------------------------ + +Update the Athenz configuration file `athenz.conf` in `athenz-zts-X.Y/conf/zts_server` +directory to include the correct ZMS Server URL and the registered public keys. + +## Start ZTS Server +------------------- + +Set the required Athenz ROOT environment variable to the `athenz-zts-X.Y` +directory and from there start the ZTS Server by executing: + +``` +export ROOT= +sudo -E bin/zts_start.sh +``` + +Based on the sample configuration file provided, ZTS Server will be listening +on port 8443. diff --git a/docs/system_properties.md b/docs/system_properties.md new file mode 100644 index 00000000000..be4ffe80194 --- /dev/null +++ b/docs/system_properties.md @@ -0,0 +1,60 @@ +# Athenz Component System Properties +----------------------------------------- + +* [ZMS Server](#zms-server) + +## ZMS Server +------------- + +| Property Name | Default Value | Description | +| ------------- | ------------- | ----------- | +| athenz.zms.access_log_dir | $ROOT/logs/zms_server | Directory to store access log files | +| athenz.zms.access_log_name | access.yyyy_MM_dd.log | Format of the access log filename | +| athenz.zms.access_log_retain_days | 31 | Set the number of days before rotated access log files are deleted | +| athenz.zms.access_slf4j_logger | none | If specified, the server will use SLF4J logger with the specified name to log events instead of using Jetty's NCSARequestLog class. The administrator then must configure the specified logger in the logback.xml | +| athenz.zms.authz_service_fname | none | Specifies the authorized service json configuration file path. Without any configured value, the server will default to reading /home/athenz/conf/zms_server/authorized_services.json file | +| athenz.zms.conflict_retry_timeout | 60 | In case there is a concurrent update conflict, the server will retry the operation multiple times until this timeout is reached before returning a conflict status code back to the client | +| athenz.zms.db_pool_evict_idle_interval | 1000 * 60 * 30 | The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no idle object evictor thread will be run. The pool default is -1, but we're using 30 minutes to make sure the evictor thread is running | +| athenz.zms.db_pool_evict_idle_timeout | 1000 * 60 * 30 | The minimum amount of time (in milliseconds) an object may sit idle in the pool before it is eligible for eviction by the idle object evictor (if any) | +| athenz.zms.db_pool_max_idle | 8 | The maximum number of connections that can remain idle in the pool, without extra ones being released, or negative for no limit | +| athenz.zms.db_pool_max_total | 8 | The maximum number of active connections that can be allocated from this pool at the same time, or negative for no limit | +| athenz.zms.db_pool_max_ttl | 1000 * 60 * 10 | The maximum lifetime in milliseconds of a connection. After this time is exceeded the connection will fail the next activation, passivation or validation test. A value of zero or less means the connection has an infinite lifetime. | +| athenz.zms.db_pool_max_wait | -1 | The maximum number of milliseconds that the pool will wait (when there are no available connections) for a connection to be returned before throwing an exception, or -1 to wait indefinitely | +| athenz.zms.db_pool_min_idle | 0 | The minimum number of connections that can remain idle in the pool, without extra ones being created, or zero to create none | +| athenz.zms.debug.principal_authority | false | Boolean setting to specify whether or not debug Principal authority is used instead of the real one. If the | athenz.zms.debug is set to true, then this setting has no impact and the the debug authority is used. | +| athenz.zms.debug.role_authority | false | Boolean setting to specify whether or not debug Role authority is used instead of the real one. If the | athenz.zms.debug is set to true, then this setting has no impact and the the debug authority is used. | +| athenz.zms.domain_admin | none | If the datastore does not contain any domains during startup, the server will automatically create sys, sys.auth and user domains and assign the specified user as the admin for those domains | +| athenz.zms.enable_stats | true | Boolean setting to configure whether or not stat counters are enabled or not | +| athenz.zms.home | $ROOT/var/zms_server | Default home directory for ZMS Server. | +| athenz.zms.hostname | none | Specify the FQDN/hostname of the server. This value will be used as the h parameter in the ZMS generated UserTokens. It is also reported as part of the server banner notification in logs. | +| athenz.zms.http_idle_timeout | 30000 | In milliseconds how long that connector will be allowed to remain idle with no traffic before it is shutdown| +| athenz.zms.http_max_threads | 1024 | Max number of threads Jetty is allowed to spawn to handle incoming requests | +| athenz.zms.http_output_buffer_size | 32768 | The size in bytes of the output buffer used to aggregate HTTP output | +| athenz.zms.http_reqeust_header_size | 8192 | The maximum allowed size in bytes for a HTTP request header | +| athenz.zms.http_response_header_size | 8192 | The maximum allowed size in bytes for a HTTP response header | +| athenz.zms.http_send_date_header | false | Boolean setting to specify whether or not the server should include the Date in HTTP headers. | +| athenz.zms.http_send_server_version | false | Boolean setting to specify whether or not the server should send the Server header in response | +| athenz.zms.jdbcstore | none | URL where the ZMS Server will store domain json documents. jdbc:mysql://localhost:3306/zms - specifies MySQL instance /home/athenz/var/zms_server - specifies file store | +| athenz.zms.jdbc_password | none | If the jdbcstore is pointing to a MySQL server then this specifies the password for the jdbc user | +| athenz.zms.jdbc_user | none | If the jdbcstore is pointing to a MySQL server then this specifies the name of the user that has full access to the zms db table | +| athenz.zms.listen_host | none | For HTTP access specifies the IP address/Host for service to listen on. This is necessary when ATS is handling TLS traffic and we need Jetty to listen on 127.0.0.1 loopback address only for HTTP connections from ATS. | +| athenz.zms.port | 10080 | Default port for HTTP access | +| athenz.zms.privatekey | none | Specifies the path to the ZMS Server's private key | +| athenz.zms.privatekey_id | 0 | Specifies the identifier of the private key | +| athenz.zms.publickey | none | Specifies the path to the ZMS Server's public key | +| athenz.zms.read_only_mode | false | If enabled, ZMS will be in maintenance read only mode where only get operations will succeed and all other put, post and delete operations will be rejected with invalid request error. | +| athenz.zms.retry_delay_timeout | 50 | When ZMS determines that updating a domain json document will cause a concurrent update issue and needs to retry the operation, it will sleep configured number of milliseconds before retrying. | +| athenz.zms.signed_policy_timeout | 604800 | Specified in seconds how long the signed policy documents are valid for | +| athenz.zms.ssl_excluded_protocols | SSLv2,SSLv3 | Comma separated list of excluded ssl protocols | +| athenz.zms.ssl_key_manager_password | none | Key Manager password | +| athenz.zms.ssl_key_store | none | The path to the keystore file that contains the server's certificate | +| athenz.zms.ssl_key_store_password | none | Keystore password | +| athenz.zms.ssl_key_store_type | PKCS12 | Specifies the keystore type | +| athenz.zms.ssl_trust_store | none | The path to the trust store file that contains CA certificates | +| athenz.zms.ssl_trust_store_password | none | Trust store password | +| athenz.zms.ssl_trust_store_type | PKCS12 | Specifies the trust store type | +| athenz.zms.tls_port | 0 | Default port for HTTPS access | +| athenz.zms.user_token_timeout | 3600 | Specifies in seconds how long would the User Tokens be valid for | +| athenz.zms.virtual_domain_limit | 2 | If virtual domain support is enabled, this setting specifies the number of sub domains in the user's virtual namespace that are allowed to be created. Value of 0 indicates no limit.| +| athenz.zms.virtual_domain_support | true | Boolean setting to configure whether or not virtual domains are supported or not. These are domains created in the user's own "user" namespace | +| athenz.zms.zms_dbtable | zms_root | If the jdbcstore is pointing to a MySQL server then this specifies the DB table name. If the jdbcstore is pointing to a filestore directory then this specifies the subdirectory name | diff --git a/docs/system_view.md b/docs/system_view.md new file mode 100644 index 00000000000..81cecd01df0 --- /dev/null +++ b/docs/system_view.md @@ -0,0 +1,75 @@ +# Architecture - System View +---------------------------- + +* [System Diagram](#system-diagram) + * [ZMS](#zms-authz-management-system) + * [ZTS](#zts-authz-token-system) + * [SIA](#sia-service-identity-agent-provider) + * [ZPE](#zpe-authz-policy-engine) + * [ZPU](#zpu-authz-policyengine-updater) + +## System Diagram +----------------- + +Before we go into the details of the authorization flow, it's important +to understand the subsystems involved. The authorization (AuthZ) system +shown in the figure below consists of several logical subsystems, which +we will elaborate on in the following section. + +![Athenz System View](images/system_view.png) + +### ZMS (authZ Management System) +--------------------------------- + +ZMS is the source of truth for domains, roles, and policies for +centralized authorization. In addition to allowing CRUD operations +on the basic entities, ZMS provides an API to replicate the entities, +per domain, to ZTS. ZMS supports a centralized call to check if a +principal has access to a resource both for internal management +system checks, as well as a simple centralized deployment. Because +ZMS supports service identities, ZMS can authenticate services. + +For centralized authorization, ZMS may be the only Athenz subsystem +that you need to interact with. + +### ZTS (authZ Token System) +---------------------------- + +ZTS, the authentication token service, is only needed to support +decentralized or data plane functionality. In many ways, ZTS is like a +local replica of ZMS's data to check a principal's authentication and +confirm membership in roles within a domain. The authentication is in +the form of a signed ZToken that can be presented to any decentralized +service that wants to authorize access efficiently. Multiple ZTS +instances can be distributed to different locations as needed to scale +for issuing tokens. + +### SIA (Service Identity Agent) Provider +----------------------------------------- + +SIA (Service Identity Agent) Provider is part of the container, +although likely built with Athenz libraries. As services are +authenticated by their private keys, the job of the SIA Provider +is to generate a NToken and sign it with the given private key so +that the service can present that NToken to ZMS/ZTS as its identity +credentials. The corresponding public key must be registered in +ZMS so Athenz services can validate the signature. + +### ZPE (AuthZ Policy Engine) +----------------------------- + +Like ZTS, ZPE, the authorization policy engine is only needed to support +the decentralized authorization. ZPE is the subsystem of Athenz that +evaluates policies for a set of roles to yield an allowed or a denied +response. + +ZPE is a library that your service calls and only refers to a local +policy cache for your services domain (a small amount of data). + +### ZPU (AuthZ PolicyEngine Updater) +------------------------------------ + +Like ZTS and ZPE, ZPU is only needed to support the decentralized +authorization. The policy updater is the utility that retrieves from ZTS +the policy files for provisioned domains on a host, which ZPE uses to +evaluate access requests. diff --git a/docs/zms_client.md b/docs/zms_client.md new file mode 100644 index 00000000000..283c288ec3f --- /dev/null +++ b/docs/zms_client.md @@ -0,0 +1,314 @@ +# ZMS Client Utility +--------------------- + +* [Overview](#overview) +* [Prerequisites](#prerequisites) +* [Getting Help](#getting-help) +* [Specifying ZMS Environments](#specifying-zms-environments) +* [How the ZMS Client Authenticates](#how-the-zms-client-authenticates) +* [Listing registered domains in Athenz](#listing-registered-domains-in-athenz) +* [Displaying Administrators for a Product Domain](#displaying-administrators-for-a-product-domain) +* [Adding Domains](#adding-domains) + * [Parameters](#parameters) + * [Examples](#examples) +* [Registering Personal Domains](#registering-personal-domains) +* [Adding and Removing Administrators](#adding-and-removing-administrators) +* [Adding a Group Role](#adding-a-group-role) + * [Parameters](#parameters-1) + * [Example](#example) +* [Managing a Group Role Membership](#managing-a-group-role-membership) + * [Parameters](#parameters-2) + * [Example](#example-1) +* [Adding a Policy](#adding-a-policy) + * [Parameters](#parameters-3) + * [Example](#example-2) + +## Overview +---------- + +The ZMS client utility allows administrators to manage Athenz domains, +to check domain details, create personal domains, and add other +administrators. + +## Prerequisites +---------------- + +Before you can use the ZMS client utility, you need to have +asked the Athenz administrators to create your top level domain. + +## Getting Help +--------------- + +The help argument for the utility will display all commands available +through utility. + + $ zms-cli help + +To get additional details about a specific command: + + $ zms-cli help [command] + +For example, to get complete details on how to add a domain including +examples: + + $ zms-cli help add-domain + +## Specifying ZMS Environments +------------------------------ + +Use the `-z` option to point to the required environment for executing commands. + + $ zms-cli -z https://zms-server.athenzcompany.com:4443/zms/v1 -d athenz show-domain + + +## How the ZMS Client Authenticates +----------------------------------- + +The Athenz ZMS server requires the user to provide its `UserToken` to +authorize the requests. The `UserToken` is obtained from the ZMS Server +with the user’s credentials and is valid for one hour. + +Before communicating with the ZMS Server, the `zms-cli` utility: + +1. prompts for the user's password +2. generates an Authorization header and passes it to ZMS to retrieve a `UserToken` + +After communicating with the ZMS Server, the `zms-cli` utility caches +the user's NToken in the user's home directory in the file `.ntoken`. +It will continue to use the token until it expires, and then will prompt +for the user's password. + +## Listing registered domains in Athenz +--------------------------------------- + +To find out what domains have been provisioned in Athenz, run the +following: + + $ zms-cli list-domain + +The list-domain command also takes an optional prefix argument to filter +the domains and only return those that start with the specified string. +For example, if the user wants to list only the domains that have been +configured in the `athenz` product domain, he/she will run the following +command: + + $ zms-cli list-domain athenz + domains: + - athenz + - athenz.ci + - athenz.qa + +## Displaying Administrators for a Product Domain +------------------------------------------------- + +To view the full list of administrators for a given domain, run the +following: + + $ zms-cli -d show-role admin + +For example, to view the Athenz system administrators for the domain +`media.news`: + + $ zms-cli -d media.news show-role admin + +## Adding Domains +----------------- + +The Top Level Product Domains in ZMS are provisioned by Athenz +administrators. + +Once the Product Domain has been created, the designated domain +administrators can create any subdomains as necessary. + +To create a new domain, the administrator uses the `add-domain` command: + + $ zms-cli add-domain [ ...] + +### Parameters + + + +The name of the domain to be added, which could either be a product +domain or a subdomain. For example, to add a subdomain called `storage` +in the `athenz` domain, the value for `` would be +`athenz.storage`. The parent domain must exist before a subdomain +can be created. The domain name can only include any letters, digits and +the '-' character. The maximum length of the domain name (including all +subdomain parts) is 256 bytes. + + [ ...] + +A space-separated list of administrators for the domain. The user +creating the domain is automatically added as an administrator. + +### Examples + +If user `joe` executes the command below, the product domain `coretech` +is created and will include `user.joe` as an administrator: + + $ zms-cli add-domain coretech + +When user `joe` executes the command below, the sub domain `athenz.ci` +is created and will have the administrators `yby.joe`, `yby.john`, and +`yby.jane`: + + $ zms-cli add-domain athenz.ci yby.john yby.jane + +## Registering Personal Domains +------------------------------- + +ZMS supports Personal Domains. These domains are provisioned for users +in the User `user` domain and have the same functionality as Product +Domains in Athenz. The Personal Domain uses the syntax `user.` +and can have configured number of subdomains (default 2). + +For example, if your user ID is joe and you'd like to create a +Personal Domain, you would run the following command: + + $ zms-cli add-domain user.joe + +Then, to create a subdomain called `athenz-test` in your Personal +Domain, you would run the following command: + + $ zms-cli add-domain user.joe.athenz-test + +If the user wants to list only his/her personal domains that have been +registered, he/she will run the following command: + + $ zms-cli list-domain user. + +## Adding and Removing Administrators +------------------------------------- + +Domain administrators are the principals listed as members of the role +`admin`. When you create a domain, the role and corresponding policy +`role` are created. Administrators can manage the list of current domain +administrators by adding or removing members in the `admin` role. + +To add one or more administrators: + + $ zms-cli -d add-member admin [ ...] + +To remove existing domain administrators: + + $ zms-cli -d delete-member admin [ ...] + +ZMS allows you to remove yourself from the `admin` role. + + Once you've been removed, you'll need to ask another domain + administrator to re-add you to the `admin` role. + +## Adding a Group Role +---------------------- + +To add new group role to a domain, the administrator will execute the +following zms-cli command: + + $ zms-cli -d add-group-role [ ...] + +### Parameters + + + +The name of the domain that the new role belongs to. + + + +The name of the new role to be added. + + [ ...] + +A space-separated list of members for the role. At least one member must +be specified. If the member is a regular user, then user's id +must be prefixed with `user.`. Once the group has been created, the +administrator can add and/or delete members using the `add-member` and +`delete-member` commands. + +### Example + +When the domain administrator executes the command below, a new role +called `readers` will be added to the the domain `athenz.ci` will +have the following members: user - `yby.john` and service - +`media.sports.storage`: + + $ zms-cli -d athenz.ci add-group-role readers yby.john media.sports.storage + +## Managing a Group Role Membership +----------------------------------- + +To add and/or delete members to/from a given role in a domain, the +administrator will execute the following zms-cli commands: + + $ zms-cli -d add-member [ ...] + $ zms-cli -d delete-member [ ...] + +### Parameters + + + +The name of the domain that the role belongs to. + + + +The name of the role that will be modified to add or remove members. + + [ ...] + +A space-separated list of members to be added to the role or to be +removed from the role. At least one member must be specified. If the +member is a regular user, the user's id must be prefixed with +`user.`. + +When specifying service identities as members you must provide +the full service name in then <domain-name>.<service-name> format. + +### Example + +To add two new members: service "media.sports.storage" and user +"yby.john", to a role called "readers" in the domain "athenz", the +domain administrator will execute the following command: + + $ zms-cli -d athenz add-member readers yby.john media.sports.storage + +To delete member "media.sports.storage" from a role called "writers" in +the domain "athenz", the domain administrator will execute the +following command: + + $ zms-cli -d athenz delete-member writers media.sports.storage + +## Adding a Policy +------------------ + +To add new policy to a domain, the administrator will execute the +following zms-cli command: + + $ zms-cli -d add-policy [] + +### Parameters + + + +The name of the domain that the new policy belongs to. + + + +The name of the new policy to be added. + + [] where is ' to on ' + +The value effect must be either 'grant' or 'deny'. The action is the +domain administrator defined action available for the resource (e.g. +read, write, delete). The role is the name of the role this assertion +applies to. The resource is the YRN of the resource this assertion +applies to. Once the policy has been created, the administrator can add +and/or delete assertions using the `add-assertion` and +`delete-assertion` commands. + +### Example + +When the domain administrator executes the command below, a new policy +called `writers` will be added to the the domain `athenz.ci` that +will grant `write` access to all the members of the `sports_writers` +role on `articles.sports.*`: + + $ zms-cli -d athenz.ci add-policy writers grant write to sports_writers on 'articles.sports.*' diff --git a/libs/go/zmscli/README.md b/libs/go/zmscli/README.md new file mode 100644 index 00000000000..f46c61ebd86 --- /dev/null +++ b/libs/go/zmscli/README.md @@ -0,0 +1,11 @@ +zms-cli +======= + +ZMS Client application library in go to manage your Athenz domain in ZMS Server. + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() + diff --git a/libs/go/zmscli/access.go b/libs/go/zmscli/access.go new file mode 100644 index 00000000000..e2fc161def5 --- /dev/null +++ b/libs/go/zmscli/access.go @@ -0,0 +1,70 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + "fmt" + "strings" + + "../../../clients/go/zms" +) + +func (cli Zms) ShowAccess(dn string, action string, resource string, altIdent *string, altDomain *string) (*string, error) { + yrn := resource + idx := strings.Index(resource, ":") + if idx < 0 { + yrn = dn + ":" + resource + } else { + // special handling for assume_role case where the domain in + // the resource does not have to match the value specified + // in the domain argument. + action = strings.ToLower(action) + if action != "assume_role" { + resDomain := resource[0:idx] + if resDomain != dn { + return nil, fmt.Errorf("Domain name mismatch. Expected " + dn + ", encountered in resource " + resDomain) + } + } + } + altPrincipal := "" + if altIdent != nil { + altPrincipal = *altIdent + if strings.Index(altPrincipal, ".") < 0 { + altPrincipal = cli.UserDomain + "." + altPrincipal + } + } + trustDomain := "" + if altDomain != nil { + trustDomain = *altDomain + } + access, err := cli.Zms.GetAccess(zms.ActionName(action), zms.YRN(yrn), zms.DomainName(trustDomain), zms.EntityName(altPrincipal)) + if err != nil { + return nil, err + } + s := "access: granted" + if !access.Granted { + s = "access: denied" + } + return &s, nil +} + +func (cli Zms) ShowResourceAccess(principal string, action string) (*string, error) { + rsrcAccessList, err := cli.Zms.GetResourceAccessList(zms.EntityName(principal), zms.ActionName(action)) + if err != nil { + return nil, err + } + var buf bytes.Buffer + buf.WriteString("resource-access:\n") + for _, rsrc := range rsrcAccessList.Resources { + buf.WriteString(indent_level1_dash + "principal: " + string(rsrc.Principal) + "\n") + buf.WriteString(indent_level1 + " assertions:\n") + indent2 := indent_level1 + " - " + for _, assertion := range rsrc.Assertions { + cli.dumpAssertion(&buf, assertion, "", indent2) + } + } + s := buf.String() + return &s, nil +} diff --git a/libs/go/zmscli/cli.go b/libs/go/zmscli/cli.go new file mode 100644 index 00000000000..5a03f08d84f --- /dev/null +++ b/libs/go/zmscli/cli.go @@ -0,0 +1,1524 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + "encoding/base64" + "fmt" + "io/ioutil" + "net/http" + "strconv" + "strings" + + "../../../clients/go/zms" +) + +type Zms struct { + ZmsUrl string + Identity string + Verbose bool + Bulkmode bool + Interactive bool + Zms zms.ZMSClient + Domain string + AuditRef string + UserDomain string + ProductIdSupport bool + Debug bool +} + +func (cli *Zms) SetClient(tr *http.Transport, authHeader *string, ntoken *string) { + cli.Zms = zms.NewClient(cli.ZmsUrl, tr) + cli.Zms.AddCredentials(*authHeader, *ntoken) +} + +func (cli Zms) interactiveSingleQuoteString(interactive bool, value string) string { + retValue := value + if !interactive { + retValue = "'" + value + "'" + } + return retValue +} + +func (cli Zms) getInt32(str string) (int32, error) { + value, err := strconv.ParseInt(str, 10, 32) + if err != nil { + return -1, err + } + return int32(value), nil +} + +func (cli *Zms) EvalCommand(params []string) (*string, error) { + if len(params) >= 1 { + cmd := params[0] + args := params[1:] + argc := len(args) + dn := cli.Domain + //first do commands that do not require the domain to be set in the context + switch cmd { + case "list-domain", "list-domains", "domain", "domains": + if argc == 0 { + return cli.ListDomains(nil, "", "", nil) + } else if argc == 1 { + prefix := args[0] + return cli.ListDomains(nil, "", prefix, nil) + } else if argc == 4 { + var err error + var limit, depth int32 + limit, err = cli.getInt32(args[0]) + if err != nil { + return nil, err + } + skip := args[1] + prefix := args[2] + depth, err = cli.getInt32(args[3]) + if err != nil { + return nil, err + } + return cli.ListDomains(&limit, skip, prefix, &depth) + } + return cli.helpCommand(params) + case "lookup-domain-by-role": + if argc == 2 { + return cli.LookupDomainByRole(args[0], args[1]) + } + return cli.helpCommand(params) + case "lookup-domain-by-aws-account", "lookup-domain-by-account": + if argc == 1 { + return cli.LookupDomainById(args[0], nil) + } + return cli.helpCommand(params) + case "lookup-domain-by-product-id": + if argc == 1 { + productId, err := cli.getInt32(args[0]) + if err != nil { + return nil, err + } + return cli.LookupDomainById("", &productId) + } + return cli.helpCommand(params) + case "use-domain": + var s string + if argc > 0 { + cli.Domain = args[0] + s = "[now working with the " + cli.Domain + " domain]" + } else { + cli.Domain = "" + s = "[not using any domain]" + } + return &s, nil + case "show-domain": + if argc == 1 { + //override the default domain, this command can show any of them + dn = args[0] + } + if dn != "" { + return cli.ShowDomain(dn) + } else { + return nil, fmt.Errorf("No domain specified") + } + case "check-domain": + if argc == 1 { + //override the default domain, this command can check any of them + dn = args[0] + } + if dn != "" { + return cli.CheckDomain(dn) + } else { + return nil, fmt.Errorf("No domain specified") + } + case "export-domain": + if argc == 1 || argc == 2 { + dn = args[0] + yamlfile := "-" + if argc == 2 { + yamlfile = args[1] + } + return cli.ExportDomain(dn, yamlfile) + } + return cli.helpCommand(params) + case "import-domain": + if argc >= 1 { + dn = args[0] + yamlfile := "-" + admins := args[0:0] + if argc >= 2 { + yamlfile = args[1] + admins = args[2:] + } + cli.Bulkmode = true + return cli.ImportDomain(dn, yamlfile, admins) + } + return cli.helpCommand(params) + case "update-domain": + if argc >= 1 { + dn = args[0] + yamlfile := "-" + if argc >= 2 { + yamlfile = args[1] + } + cli.Bulkmode = true + return cli.UpdateDomain(dn, yamlfile) + } + return cli.helpCommand(params) + case "system-backup": + if argc == 1 { + return cli.SystemBackup(args[0]) + } + return cli.helpCommand(params) + case "add-domain": + if argc > 0 { + dn = args[0] + if cli.ProductIdSupport && strings.LastIndex(dn, ".") < 0 { + if argc < 2 { + return nil, fmt.Errorf("Top Level Domains require a number specified for the product id") + } + productId, err := cli.getInt32(args[1]) + if err != nil { + return nil, fmt.Errorf("Top Level Domains require an integer number specified for the product id") + } + return cli.AddDomain(dn, &productId, args[2:]) + } + return cli.AddDomain(dn, nil, args[1:]) + } + return cli.helpCommand(params) + case "delete-domain": + if argc == 1 { + if args[0] == dn { + return nil, fmt.Errorf("Cannot delete domain while using it") + } + return cli.DeleteDomain(args[0]) + } + return cli.helpCommand(params) + case "set-default-admins": + if argc >= 1 { + return cli.SetDefaultAdmins(args[0], args[1:]) + } + case "get-signed-domains": + matchingTag := "" + if argc == 1 { + matchingTag = args[0] + } + return cli.GetSignedDomains("", matchingTag) + case "list-server-template", "list-server-templates": + return cli.ListServerTemplates() + case "list-domain-template", "list-domain-templates": + if argc == 1 { + //override the default domain, this command can show any of them + dn = args[0] + } + if dn != "" { + return cli.ListDomainTemplates(dn) + } else { + return nil, fmt.Errorf("No domain specified") + } + case "show-server-template": + if argc == 1 { + return cli.ShowServerTemplate(args[0]) + } + case "show-resource": + if argc == 2 { + return cli.ShowResourceAccess(args[0], args[1]) + } + case "help": + return cli.helpCommand(args) + default: + //the rest all rely on the domain being defined + if dn == "" { + var err error + if cli.Interactive { + err = fmt.Errorf("No domain specified. Use use-domain command to set the domain") + } else { + err = fmt.Errorf("No domain specified. Use -d argument to specify the domain. Type 'zms-cli' to see help information") + } + return nil, err + } + //and fall through + } + + switch cmd { + + case "list-policy", "list-policies": + return cli.ListPolicies(dn) + case "show-policy": + if argc == 1 { + return cli.ShowPolicy(dn, args[0]) + } + case "add-policy", "set-policy": + if argc >= 1 { + return cli.AddPolicy(dn, args[0], args[1:]) + } + case "add-assertion": + if argc >= 1 { + return cli.AddAssertion(dn, args[0], args[1:]) + } + case "delete-assertion": + if argc >= 1 { + return cli.DeleteAssertion(dn, args[0], args[1:]) + } + case "delete-policy": + if argc == 1 { + return cli.DeletePolicy(dn, args[0]) + } + case "show-access": + if argc >= 2 { + var trustDomain *string + var altPrincipal *string + if argc > 2 { + altPrincipal = &args[2] + if argc > 3 { + trustDomain = &args[3] + } + } + return cli.ShowAccess(dn, args[0], args[1], altPrincipal, trustDomain) + } + case "list-role", "list-roles": + return cli.ListRoles(dn) + case "show-role": + if argc == 1 { + return cli.ShowRole(dn, args[0], false, false) + } else if argc == 2 && args[1] == "log" { + return cli.ShowRole(dn, args[0], true, false) + } else if argc == 2 && args[1] == "expand" { + return cli.ShowRole(dn, args[0], false, true) + } + case "add-delegated-role", "add-trusted-role": + if argc == 2 { + return cli.AddDelegatedRole(dn, args[0], args[1]) + } + case "add-group-role": + if argc >= 1 { + return cli.AddGroupRole(dn, args[0], args[1:]) + } + case "add-provider-role-member", "add-provider-role-members": + if argc >= 4 { + return cli.AddProviderRoleMembers(dn, args[0], args[1], args[2], args[3:]) + } + case "show-provider-role-member", "show-provider-role-members": + if argc == 3 { + return cli.ShowProviderRoleMembers(dn, args[0], args[1], args[2]) + } + case "delete-provider-role-member", "delete-provider-role-members": + if argc >= 4 { + return cli.DeleteProviderRoleMembers(dn, args[0], args[1], args[2], args[3:]) + } + case "add-member", "add-members": + if argc >= 2 { + return cli.AddMembers(dn, args[0], args[1:]) + } + case "delete-member", "delete-members": + if argc >= 2 { + return cli.DeleteMembers(dn, args[0], args[1:]) + } + case "check-member", "check-members", "show-member", "show-members": + if argc >= 2 { + return cli.CheckMembers(dn, args[0], args[1:]) + } + case "delete-role": + if argc == 1 { + return cli.DeleteRole(dn, args[0]) + } + case "list-service", "list-services": + if argc == 0 { + return cli.ListServices(dn) + } + case "show-service": + if argc == 1 { + return cli.ShowService(dn, args[0]) + } + case "add-service": + if argc == 3 { + pubkey, err := cli.getPublicKey(args[2]) + if err == nil { + return cli.AddService(dn, args[0], args[1], pubkey) + } else { + return nil, err + } + } + case "add-provider-service": + if argc == 3 { + pubkey, err := cli.getPublicKey(args[2]) + if err == nil { + return cli.AddProviderService(dn, args[0], args[1], pubkey) + } else { + return nil, err + } + } + case "set-service-endpoint": + if argc == 2 { + return cli.SetServiceEndpoint(dn, args[0], args[1]) + } + case "set-service-exe": + if argc == 4 { + return cli.SetServiceExe(dn, args[0], args[1], args[2], args[3]) + } + case "add-service-host": + if argc >= 2 { + return cli.AddServiceHost(dn, args[0], args[1:]) + } + case "delete-service-host": + if argc >= 2 { + return cli.DeleteServiceHost(dn, args[0], args[1:]) + } + case "add-public-key": + if argc == 3 { + pubkey, err := cli.getPublicKey(args[2]) + if err == nil { + return cli.AddServicePublicKey(dn, args[0], args[1], pubkey) + } else { + return nil, err + } + } + case "show-public-key": + if argc == 2 { + return cli.ShowServicePublicKey(dn, args[0], args[1]) + } + case "delete-public-key": + if argc == 2 { + return cli.DeleteServicePublicKey(dn, args[0], args[1]) + } + case "delete-service": + if argc == 1 { + return cli.DeleteService(dn, args[0]) + } + case "list-entity", "list-entities": + if argc == 0 { + return cli.ListEntities(dn) + } + case "add-entity": + if argc > 1 { + return cli.AddEntity(dn, args[0], args[1:]) + } + case "delete-entity": + if argc == 1 { + return cli.DeleteEntity(dn, args[0]) + } + case "show-entity": + if argc == 1 { + return cli.ShowEntity(dn, args[0]) + } + case "show-tenancy": + if argc == 1 { + return cli.ShowTenancy(dn, args[0]) + } + case "add-tenancy": + if argc == 1 { + return cli.AddTenancy(dn, args[0]) + } + case "delete-tenancy": + if argc == 1 { + return cli.DeleteTenancy(dn, args[0]) + } + case "add-tenancy-resource-group": + if argc == 2 { + return cli.AddTenancyResourceGroup(dn, args[0], args[1]) + } + case "delete-tenancy-resource-group": + if argc == 2 { + return cli.DeleteTenancyResourceGroup(dn, args[0], args[1]) + } + case "show-tenant-roles": + if argc == 2 { + return cli.ShowTenantRoles(dn, args[0], args[1]) + } + case "add-tenant-roles": + if argc > 2 { + return cli.AddTenantRoles(dn, args[0], args[1], args[2:]) + } + case "delete-tenant-roles": + if argc == 2 { + return cli.DeleteTenantRoles(dn, args[0], args[1]) + } + case "show-tenant-resource-group-roles": + if argc == 3 { + return cli.ShowTenantResourceGroupRoles(dn, args[0], args[1], args[2]) + } + case "add-tenant-resource-group-roles": + if argc > 3 { + return cli.AddTenantResourceGroupRoles(dn, args[0], args[1], args[2], args[3:]) + } + case "delete-tenant-resource-group-roles": + if argc == 3 { + return cli.DeleteTenantResourceGroupRoles(dn, args[0], args[1], args[2]) + } + case "show-provider-resource-group-roles": + if argc == 3 { + return cli.ShowProviderResourceGroupRoles(dn, args[0], args[1], args[2]) + } + case "add-provider-resource-group-roles": + if argc > 3 { + return cli.AddProviderResourceGroupRoles(dn, args[0], args[1], args[2], args[3:]) + } + case "delete-provider-resource-group-roles": + if argc == 3 { + return cli.DeleteProviderResourceGroupRoles(dn, args[0], args[1], args[2]) + } + case "set-domain-meta": + if argc == 3 { + descr := args[0] + org := args[1] + auditEnabled, err := strconv.ParseBool(args[2]) + if err != nil { + return nil, err + } + return cli.SetDomainMeta(dn, descr, org, auditEnabled) + } + case "set-aws-account", "set-domain-account": + if argc == 1 { + return cli.SetDomainAccount(dn, args[0]) + } + case "set-product-id", "set-domain-product-id": + if argc == 1 { + productId, err := cli.getInt32(args[0]) + if err != nil { + return nil, err + } + return cli.SetDomainProductId(dn, productId) + } + case "set-domain-template": + if argc >= 1 { + return cli.SetDomainTemplate(dn, args[0:]) + } + case "delete-domain-template": + if argc == 1 { + return cli.DeleteDomainTemplate(dn, args[0]) + } + case "add-profile": + if argc == 1 { + return cli.AddProfile(dn, args[0]) + } + case "show-profile": + if argc == 1 { + return cli.ShowProfile(dn, args[0]) + } + default: + return nil, fmt.Errorf("Unrecognized command '%v'. Type 'zms-cli help' to see help information", cmd) + } + return nil, fmt.Errorf("Bad command syntax. Type 'zms-cli help' to see help information") + } + return cli.helpCommand(params) +} + +func (cli Zms) helpCommand(params []string) (*string, error) { + s := "" + if params != nil && len(params) == 1 { + s = cli.HelpSpecificCommand(cli.Interactive, params[0]) + } else { + s = cli.HelpListCommand() + } + return &s, nil +} + +func (cli Zms) HelpSpecificCommand(interactive bool, cmd string) string { + var buf bytes.Buffer + + // we are going to display the required domain argument + // only in non-interactive mode + domain_param := "-d domain" + domain_example := "-d coretech" + tenant_example := "-d sports" + if interactive { + domain_param = "" + domain_example = "coretech> " + tenant_example = "sports> " + } + + switch cmd { + case "list-domain": + buf.WriteString(" syntax:\n") + buf.WriteString(" list-domain [prefix]\n") + buf.WriteString(" list-domain [limit skip prefix depth]\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" prefix : return domains starting with this value \n") + buf.WriteString(" limit : return specified number of domains only\n") + buf.WriteString(" skip : exclude all the domains including the specified one from the return set\n") + buf.WriteString(" depth : maximum depth of the domains returned (0 - top level domains only)\n") + buf.WriteString(" examples:\n") + buf.WriteString(" list-domain cd\n") + buf.WriteString(" return all domains who names start with cd\n") + case "show-domain": + buf.WriteString(" syntax:\n") + buf.WriteString(" show-domain domain\n") + buf.WriteString(" " + domain_param + " show-domain\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : retrieve roles, policies and services for this domain\n") + buf.WriteString(" : this argument is required unless -d is specified or\n") + buf.WriteString(" : running in repl/interactive mode with use-domain specified\n") + buf.WriteString(" examples:\n") + buf.WriteString(" show-domain coretech.hosted\n") + buf.WriteString(" " + domain_example + " show-domain\n") + case "lookup-domain-by-account", "lookup-domain-by-aws-account": + buf.WriteString(" syntax:\n") + buf.WriteString(" lookup-domain-by-aws-account account-id\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" account-id : lookup domain with specified account id\n") + buf.WriteString(" examples:\n") + buf.WriteString(" lookup-domain-by-aws-account 1234567890\n") + case "lookup-domain-by-prod-id": + buf.WriteString(" syntax:\n") + buf.WriteString(" lookup-domain-by-prod-id product-id\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" prod-id : lookup domain with specified product id\n") + buf.WriteString(" examples:\n") + buf.WriteString(" lookup-domain-by-prod-id 10001\n") + case "lookup-domain-by-role": + buf.WriteString(" syntax:\n") + buf.WriteString(" lookup-domain-by-role role-member role-name\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" role-member : name of the principal\n") + buf.WriteString(" role-name : name of the role where the principal is a member of\n") + buf.WriteString(" examples:\n") + buf.WriteString(" lookup-domain-by-role " + cli.UserDomain + ".joe admin\n") + case "check-domain": + buf.WriteString(" syntax:\n") + buf.WriteString(" check-domain domain\n") + buf.WriteString(" " + domain_param + " check-domain\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : verify domain resources and report any issues\n") + buf.WriteString(" : this argument is required unless -d is specified or\n") + buf.WriteString(" : running in repl/interactive mode with use-domain specified\n") + buf.WriteString(" examples:\n") + buf.WriteString(" check-domain coretech.hosted\n") + buf.WriteString(" " + domain_example + " check-domain\n") + case "use-domain": + buf.WriteString(" syntax:\n") + buf.WriteString(" use-domain [domain]\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : when running in repl mode sets the domain value for all operations\n") + buf.WriteString(" : passing \"\" domain will reset the client's saved domain value\n") + buf.WriteString(" examples:\n") + buf.WriteString(" use-domain coretech\n") + case "add-domain": + buf.WriteString(" syntax:\n") + buf.WriteString(" add-domain domain [prod-id] [admin ...]\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : name of the domain to be added\n") + buf.WriteString(" : The name can be either a top level domain or a subdomain\n") + buf.WriteString(" prod-id : unique product id number required when creating top level domains\n") + buf.WriteString(" admin : list of domain administrators separated by a space\n") + buf.WriteString(" : the user creating the domain will be added as an administrator\n") + buf.WriteString(" examples:\n") + buf.WriteString(" add-domain coretech john jane\n") + buf.WriteString(" add a top level domain called coretech with " + cli.UserDomain + ".john, " + cli.UserDomain + ".jane and the caller as administrators\n") + buf.WriteString(" add-domain coretech.hosted john jane\n") + buf.WriteString(" add a subdomain hosted in domain coretech with " + cli.UserDomain + ".john, " + cli.UserDomain + ".jane and the caller as administrators\n") + case "set-domain-meta": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " set-domain-meta description org audit_enabled\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain being updated\n") + } + buf.WriteString(" description : set the description for the domain\n") + buf.WriteString(" org : set the organization of the domain\n") + buf.WriteString(" audit_enabled : boolean flag indicating if the domain must comply with SOX auditing requirements\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " set-domain-meta \"Coretech Hosted\" cloud.services false\n") + case "set-aws-account", "set-domain-account": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " set-aws-account account-id\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain being updated\n") + } + buf.WriteString(" account-id : set the aws account id for the domain\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " set-aws-account \"134901934383\"\n") + case "set-prod-id", "set-domain-prod-id": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " set-prod-id prod-id\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain being updated\n") + } + buf.WriteString(" prod-id : set the Product ID for the domain\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " set-prod-id 10001\n") + case "import-domain": + buf.WriteString(" syntax:\n") + buf.WriteString(" import-domain domain [file.yaml [admin ...]] - no file means stdin\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : name of the domain being imported\n") + buf.WriteString(" file.yaml : file that contains domain contents in yaml format\n") + buf.WriteString(" admin : additional list of administrators to be added to the domain\n") + buf.WriteString(" examples:\n") + buf.WriteString(" import-domain coretech coretech.yaml " + cli.UserDomain + ".john\n") + case "update-domain": + buf.WriteString(" syntax:\n") + buf.WriteString(" update-domain domain [file.yaml] - no file means stdin\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : name of the domain being updated\n") + buf.WriteString(" file.yaml : file that contains domain contents in yaml format\n") + buf.WriteString(" examples:\n") + buf.WriteString(" update-domain coretech coretech.yaml\n") + case "export-domain": + buf.WriteString(" syntax:\n") + buf.WriteString(" export-domain domain [file.yaml] - no file means stdout\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : name of the domain to be exported\n") + buf.WriteString(" file.yaml : filename where the domain data is stored\n") + buf.WriteString(" examples:\n") + buf.WriteString(" export-domain coretech /tmp/coretech.yaml\n") + case "delete-domain": + buf.WriteString(" syntax:\n") + buf.WriteString(" delete-domain domain\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : name of the domain to be deleted\n") + buf.WriteString(" examples:\n") + buf.WriteString(" delete-domain coretech\n") + case "set-default-admins": + buf.WriteString(" syntax:\n") + buf.WriteString(" set-default-admins domain admin [admin ...]\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : name of the domain to restore admin access\n") + buf.WriteString(" admin : list of administrators to be set for domain\n") + buf.WriteString(" examples:\n") + buf.WriteString(" set-default-admins coretech.hosted " + cli.UserDomain + ".john " + cli.UserDomain + ".jane\n") + case "get-signed-domains": + buf.WriteString(" syntax:\n") + buf.WriteString(" get-signed-domains [matching_tag]\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" matching-tag : value of ETag header retrieved from previous get-signed-domain call\n") + buf.WriteString(" : server will return changes since this timestamp only\n") + buf.WriteString(" examples:\n") + buf.WriteString(" get-signed-domains\n") + buf.WriteString(" get-signed-domains \"2015-04-10T20:43:34.023Z-gzip\"\n") + case "list-policy": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " list-policy\n") + if !interactive { + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : name of the domain to retrieve the list of policies from\n") + } + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " list-policy\n") + case "show-policy": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " show-policy policy\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that policy belongs to\n") + } + buf.WriteString(" policy : name of the policy to be displayed\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " show-policy admin\n") + case "add-policy", "set-policy": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-policy policy [assertion]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain to add policy to\n") + } + buf.WriteString(" policy : name of the policy\n") + buf.WriteString(" assertion : to on \n") + buf.WriteString(" : effect - grant or deny\n") + buf.WriteString(" : action - domain admin defined action available for the resource (e.g. read, write, delete)\n") + buf.WriteString(" : role - which role this assertion applies to\n") + buf.WriteString(" : client will prepend 'domain:role.' to role name if not specified\n") + buf.WriteString(" : resource - which resource this assertion applies to\n") + buf.WriteString(" : client will prepend 'domain:' to resource if not specified\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-policy writers_policy grant write to writers_role on articles.sports\n") + buf.WriteString(" " + domain_example + " add-policy readers_policy grant read to readers_role on " + cli.interactiveSingleQuoteString(interactive, "articles.*") + "\n") + case "add-assertion": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-assertion policy assertion\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that policy belongs to\n") + } + buf.WriteString(" policy : name of the policy to add this assertion to\n") + buf.WriteString(" assertion : to on \n") + buf.WriteString(" : effect - grant or deny\n") + buf.WriteString(" : action - domain admin defined action available for the resource (e.g. read, write, delete)\n") + buf.WriteString(" : role - which role this assertion applies to\n") + buf.WriteString(" : client will prepend 'domain:role.' to role name if not specified\n") + buf.WriteString(" : resource - which resource this assertion applies to\n") + buf.WriteString(" : client will prepend 'domain:' to resource if not specified\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-assertion writers_policy grant write to writers_role on articles.sports\n") + buf.WriteString(" " + domain_example + " add-assertion readers_policy grant read to readers_role on " + cli.interactiveSingleQuoteString(interactive, "articles.*") + "\n") + case "delete-assertion": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-assertion policy assertion\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that policy belongs to\n") + } + buf.WriteString(" policy : name of the policy to delete this assertion from\n") + buf.WriteString(" assertion : existing assertion in the policy in the ' to on ' format\n") + buf.WriteString(" : the value must be exactly what's displayed when executing the show-policy command\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-assertion writers_policy grant write to writers_role on articles.sports\n") + buf.WriteString(" " + domain_example + " delete-assertion readers_policy grant read to readers_role on " + cli.interactiveSingleQuoteString(interactive, "articles.*") + "\n") + case "delete-policy": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-policy policy\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that policy belongs to\n") + } + buf.WriteString(" policy : name of the policy to be deleted\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-policy readers\n") + case "show-access": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " show-access action resource [alt_identity [trust_domain]]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that resource belongs to\n") + } + buf.WriteString(" action : access check action value\n") + buf.WriteString(" resource : access check resource (yrn)\n") + buf.WriteString(" : client will prepend 'domain:' to resource if not specified\n") + buf.WriteString(" alt_identity : run the access check for this identity instead of the caller\n") + buf.WriteString(" trust_domain : when checking for cross-domain trust relationship\n") + buf.WriteString(" : only check this trusted domain\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " show-access node_sudo node.host1\n") + buf.WriteString(" " + domain_example + " show-access node_sudo coretech:node.host1\n") + buf.WriteString(" " + domain_example + " show-access node_sudo coretech:node.host1 " + cli.UserDomain + ".john\n") + case "show-resource": + buf.WriteString(" syntax:\n") + buf.WriteString(" show-resource principal action\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" principal : show resources for this principal only\n") + buf.WriteString(" action : assertion action value to filter on\n") + buf.WriteString(" examples:\n") + buf.WriteString(" show-resource " + cli.UserDomain + ".user1 update\n") + buf.WriteString(" show-resource " + cli.UserDomain + ".user1 \"\"\n") + buf.WriteString(" show-resource \"\" assume_aws_role\n") + case "list-role": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " list-role\n") + if !interactive { + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : name of the domain to retrieve the list of roles from\n") + } + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " list-role\n") + case "show-role": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " show-role role [log | expand]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that role belongs to\n") + } + buf.WriteString(" role : name of the role to be displayed\n") + buf.WriteString(" log : option argument to specify to display audit logs for role\n") + buf.WriteString(" expand : option argument to specify to display delegated members\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " show-role admin\n") + buf.WriteString(" " + domain_example + " show-role admin log\n") + buf.WriteString(" " + domain_example + " show-role delegated-role expand\n") + case "add-delegated-role": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-delegated-role role trusted_domain\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that role belongs to\n") + } + buf.WriteString(" role : name of the cross-domain/trust delegated role\n") + buf.WriteString(" trust_domain : name of the cross/trusted domain name\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-delegated-role tenant.sports.readers sports\n") + case "add-group-role": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-group-role role member [member ... ]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that role belongs to\n") + } + buf.WriteString(" role : name of the standard group role\n") + buf.WriteString(" member : list of group members that could be either users or services\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-group-role readers " + cli.UserDomain + ".john " + cli.UserDomain + ".joe media.sports.storage\n") + case "add-member": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-member group_role user_or_service [user_or_service ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that role belongs to\n") + } + buf.WriteString(" group-role : name of the standard group role to add members to\n") + buf.WriteString(" user_or_service : users or services to be added as members\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-member readers " + cli.UserDomain + ".john " + cli.UserDomain + ".joe media.sports.storage\n") + case "check-member": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " check-member group_role user_or_service [user_or_service ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that role belongs to\n") + } + buf.WriteString(" group-role : name of the standard group role to check membership\n") + buf.WriteString(" user_or_service : users or services to be checked if they are members\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " check-member readers " + cli.UserDomain + ".john " + cli.UserDomain + ".joe media.sports.storage\n") + case "delete-member": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-member group_role user_or_service [user_or_service ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that role belongs to\n") + } + buf.WriteString(" group-role : name of the standard group role to remove members from\n") + buf.WriteString(" user_or_service : users or services to be removed as members\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-member readers " + cli.UserDomain + ".john " + cli.UserDomain + ".joe media.sports.storage\n") + case "add-provider-role-member": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-provider-role-member provider_service resource_group provider_role user_or_service [user_or_service ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that role belongs to\n") + } + buf.WriteString(" provider_service : name of the provider service\n") + buf.WriteString(" resource_group : name of the resource group\n") + buf.WriteString(" provider_role : the provider role name\n") + buf.WriteString(" user_or_service : users or services to be added as members\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-provider-role-member storage.db stats_db access media.sports.storage\n") + case "show-provider-role-member": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " show-provider-role-member provider_service resource_group provider_role\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that role belongs to\n") + } + buf.WriteString(" provider_service : name of the provider service\n") + buf.WriteString(" resource_group : name of the resource group\n") + buf.WriteString(" provider_role : the provider role name\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " show-provider-role-member storage.db stats_db access\n") + case "delete-provider-role-member": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-provider-role-member provider_service resource_group provider_role user_or_service [user_or_service ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that role belongs to\n") + } + buf.WriteString(" provider_service : name of the provider service\n") + buf.WriteString(" resource_group : name of the resource group\n") + buf.WriteString(" provider_role : the provider role name\n") + buf.WriteString(" user_or_service : users or services to be removed as members\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-provider-role-member storage.db stats_db access media.sports.storage\n") + case "delete-role": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-role role\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that role belongs to\n") + } + buf.WriteString(" role : name of the role to be deleted\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-role readers\n") + case "list-service": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " list-service\n") + if !interactive { + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : name of the domain to retrieve the list of services from\n") + } + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " list-service\n") + case "show-service": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " show-service service\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that service belongs to\n") + } + buf.WriteString(" service : name of the service to be displayed\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " show-service storage\n") + case "add-service": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-service service key_id identity_pubkey.pem|identity_key_ybase64\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that service belongs to\n") + } + buf.WriteString(" service : name of the service to be added to the domain\n") + buf.WriteString(" key_id : identifier of the service's public key\n") + buf.WriteString(" identity_pubkey.pem : the filename of the service's public key\n") + buf.WriteString(" : either the filename or ybase64 value must be specified\n") + buf.WriteString(" identity_key_ybase64 : ybase64 encoded value of service's public key\n") + buf.WriteString(" : either the filename or ybase64 value must be specified\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-service storage v0 /tmp/storage_pub.pem\n") + buf.WriteString(" " + domain_example + " add-service storage v0 \"MIIBOgIBAAJBAOf62yl04giXbiirU8Ck\"\n") + case "add-provider-service": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-provider-service service key_id identity_pubkey.pem|identity_key_ybase64\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that service belongs to\n") + } + buf.WriteString(" service : name of the service to be added to the domain\n") + buf.WriteString(" key_id : identifier of the service's public key\n") + buf.WriteString(" identity_pubkey.pem : the filename of the service's public key\n") + buf.WriteString(" : either the filename or ybase64 value must be specified\n") + buf.WriteString(" identity_key_ybase64 : ybase64 encoded value of service's public key\n") + buf.WriteString(" : either the filename or ybase64 value must be specified\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-provider-service storage v0 /tmp/storage_pub.pem\n") + buf.WriteString(" " + domain_example + " add-provider-service storage v0 \"MIIBOgIBAAJBAOf62yl04giXbiirU8Ck\"\n") + case "set-service-endpoint": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " set-service-endpoint service endpoint\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that service belongs to\n") + } + buf.WriteString(" service : name of the service to set the tenant auto-provisioning endpoint\n") + buf.WriteString(" endpoint : the url of the provider's service to support auto-provisioning of tenants\n") + buf.WriteString(" : To remove the endpoint pass \"\" as its value\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " set-service-endpoint storage http://coretech.athenzcompany.com:4080/tableProvider\n") + case "set-service-exe": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " set-service-exe service executable user group\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that service belongs to\n") + } + buf.WriteString(" service : name of the service to set executable info\n") + buf.WriteString(" executable : full path of the service's executable\n") + buf.WriteString(" : To remove the setting pass \"\" as its value\n") + buf.WriteString(" user : the user name that the service's process runs as\n") + buf.WriteString(" : To remove the setting pass \"\" as its value\n") + buf.WriteString(" group : the group name that the service's process runs as\n") + buf.WriteString(" : To remove the setting pass \"\" as its value\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " set-service-exe storage /usr/bin/httpd nobody wheel\n") + buf.WriteString(" " + domain_example + " set-service-exe storage \"\" nobody wheel\n") + case "add-service-host": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-service-host service host [host ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that service belongs to\n") + } + buf.WriteString(" service : name of the service to add hosts to\n") + buf.WriteString(" host : fully qualified list of hosts the service is allowed to run on\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-service-host storage ct1.athenzcompany.com ct2.athenzcompany.com\n") + case "delete-service-host": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-service-host service host [host ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that service belongs to\n") + } + buf.WriteString(" service : name of the service to delete hosts from\n") + buf.WriteString(" host : fully qualified list of hosts to remove from service's allowed run list\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-service-host storage ct1.athenzcompany.com ct2.athenzcompany.com\n") + case "add-public-key": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-public-key service key_id identity_pubkey.pem|identity_key_ybase64\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that service belongs to\n") + } + buf.WriteString(" service : name of the service to add a new public key\n") + buf.WriteString(" key_id : identifier of the service's public key\n") + buf.WriteString(" identity_pubkey.pem : the filename of the service's public key\n") + buf.WriteString(" : either the filename or ybase64 value must be specified\n") + buf.WriteString(" identity_key_ybase64 : ybase64 encoded value of service's public key\n") + buf.WriteString(" : either the filename or ybase64 value must be specified\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-public-key storage v1 /tmp/storage_pub.pem\n") + buf.WriteString(" " + domain_example + " add-public-key storage v1 \"MIIBOgIBAAJBAOf62yl04giXbiirU8Ck\"\n") + case "show-public-key": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " show-public-key service key_id\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that service belongs to\n") + } + buf.WriteString(" service : name of the service to retrieve public key from\n") + buf.WriteString(" key_id : identifier of the service's public key\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " show-public-key storage v0\n") + case "delete-public-key": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-public-key service key_id\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that service belongs to\n") + } + buf.WriteString(" service : name of the service to delete public key from\n") + buf.WriteString(" key_id : identifier of the service's public key to be deleted\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-public-key storage v0\n") + case "delete-service": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-service service\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that service belongs to\n") + } + buf.WriteString(" service : name of the service to be deleted\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-service storage\n") + case "list-entity": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " list-entity\n") + if !interactive { + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : name of the domain to retrieve the list of entities from\n") + } + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " list-entity\n") + case "show-entity": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " show-entity entity\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that entity belongs to\n") + } + buf.WriteString(" entity : name of the entity to be retrieved from the domain\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " show-entity profile\n") + case "add-entity": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-entity entity key=value [key=value ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that entity belongs to\n") + } + buf.WriteString(" entity : name of the entity to be added\n") + buf.WriteString(" key : entity field name\n") + buf.WriteString(" value : entity field value\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-entity profile name=security active=yes\n") + case "delete-entity": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-entity entity\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain that entity belongs to\n") + } + buf.WriteString(" entity : name of the entity to be deleted\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-entity profile\n") + case "show-tenancy": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " show-tenancy provider\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the tenant's domain\n") + } + buf.WriteString(" provider : provider's service name to verify if the domain is a tenant\n") + buf.WriteString(" : the provider's name must be service common name in . format\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " show-tenancy weather.storage\n") + case "add-tenancy": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-tenancy provider\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the tenant's domain\n") + } + buf.WriteString(" provider : provider's service name to auto-provision tenancy for the domain\n") + buf.WriteString(" : the provider's name must be service common name in . format\n") + buf.WriteString(" : the provider must support auto-provisioning and have configured endpoint in the service\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-tenancy weather.storage\n") + case "delete-tenancy": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-tenancy provider\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the tenant's domain\n") + } + buf.WriteString(" provider : provider's service name to remove the tenant from\n") + buf.WriteString(" : the provider's name must be service common name in . format\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-tenancy weather.storage\n") + case "add-tenancy-resource-group": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-tenancy-resource-group provider resource_group\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the tenant's domain\n") + } + buf.WriteString(" provider : provider's service name to auto-provision tenancy for the domain\n") + buf.WriteString(" : the provider's name must be service common name in . format\n") + buf.WriteString(" : the provider must support auto-provisioning and have configured endpoint in the service\n") + buf.WriteString(" resource_group : name of the tenant's resource group\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " add-tenancy-resource-group weather.storage dev_group\n") + case "delete-tenancy-resource-group": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-tenancy-resource-group provider resource_group\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the tenant's domain\n") + } + buf.WriteString(" provider : provider's service name to remove the tenant from\n") + buf.WriteString(" : the provider's name must be service common name in . format\n") + buf.WriteString(" resource_group : name of the tenant's resource group\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-tenancy-resource-group weather.storage dev_group\n") + case "show-tenant-roles": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " show-tenant-roles service tenant_domain\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : provider's domain name\n") + } + buf.WriteString(" service : provider's service name\n") + buf.WriteString(" tenant_domain : name of the tenant's domain to list roles for\n") + buf.WriteString(" examples:\n") + buf.WriteString(" tenant domain: coretech\n") + buf.WriteString(" provider domain: sports\n") + buf.WriteString(" provider service: hosted\n") + buf.WriteString(" " + tenant_example + " show-tenant-roles hosted coretech\n") + case "add-tenant-roles": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-tenant-roles service tenant_domain role=action [role=action ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : provider's domain name\n") + } + buf.WriteString(" service : provider's service name\n") + buf.WriteString(" tenant_domain : name of the tenant's domain to add roles for\n") + buf.WriteString(" role : name of the role to be added\n") + buf.WriteString(" action : the action value for the role\n") + buf.WriteString(" examples:\n") + buf.WriteString(" tenant domain: coretech\n") + buf.WriteString(" provider domain: sports\n") + buf.WriteString(" provider service: hosted\n") + buf.WriteString(" " + tenant_example + " add-tenant-roles hosted coretech readers=read writers=modify\n") + case "delete-tenant-roles": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-tenant-roles service tenant_domain\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : provider's domain name\n") + } + buf.WriteString(" service : provider's service name\n") + buf.WriteString(" tenant_domain : name of the tenant's domain to remove all previously setup tenancy roles\n") + buf.WriteString(" examples:\n") + buf.WriteString(" tenant domain: coretech\n") + buf.WriteString(" provider domain: sports\n") + buf.WriteString(" provider service: hosted\n") + buf.WriteString(" " + tenant_example + " delete-tenant-roles hosted coretech\n") + case "show-tenant-resource-group-roles": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " show-tenant-resource-group-roles service tenant_domain resource_group\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : provider's domain name\n") + } + buf.WriteString(" service : provider's service name\n") + buf.WriteString(" tenant_domain : name of the tenant's domain to list roles for\n") + buf.WriteString(" resource_group : name of the tenant's resource group\n") + buf.WriteString(" examples:\n") + buf.WriteString(" tenant domain: coretech\n") + buf.WriteString(" provider domain: sports\n") + buf.WriteString(" provider service: hosted\n") + buf.WriteString(" resource group: dev_group\n") + buf.WriteString(" " + tenant_example + " show-tenant-resource-group-roles hosted coretech dev_group\n") + case "add-tenant-resource-group-roles": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-tenant-resource-group-roles service tenant_domain resource_group role=action [role=action ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : provider's domain name\n") + } + buf.WriteString(" service : provider's service name\n") + buf.WriteString(" tenant_domain : name of the tenant's domain to add roles for\n") + buf.WriteString(" resource_group : name of the tenant's resource group\n") + buf.WriteString(" role : name of the role to be added\n") + buf.WriteString(" action : the action value for the role\n") + buf.WriteString(" examples:\n") + buf.WriteString(" tenant domain: coretech\n") + buf.WriteString(" provider domain: sports\n") + buf.WriteString(" provider service: hosted\n") + buf.WriteString(" resource group: dev_group\n") + buf.WriteString(" " + tenant_example + " add-tenant-resource-group-roles hosted coretech dev_group readers=read writers=modify\n") + case "delete-tenant-resource-group-roles": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-tenant-resource-group-roles service tenant_domain resource_group\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : provider's domain name\n") + } + buf.WriteString(" service : provider's service name\n") + buf.WriteString(" tenant_domain : name of the tenant's domain\n") + buf.WriteString(" resource_group : name of the tenant's resource group\n") + buf.WriteString(" examples:\n") + buf.WriteString(" tenant domain: coretech\n") + buf.WriteString(" provider domain: sports\n") + buf.WriteString(" provider service: hosted\n") + buf.WriteString(" resource group: dev_group\n") + buf.WriteString(" " + tenant_example + " delete-tenant-resource-group-roles hosted coretech dev_group\n") + case "show-provider-resource-group-roles": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " show-provider-resource-group-roles provider_domain provider_service resource_group\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : tenant's domain name\n") + } + buf.WriteString(" provider_domain : provider's domain name\n") + buf.WriteString(" provider_service : provider's service name\n") + buf.WriteString(" resource_group : name of the tenant's resource group\n") + buf.WriteString(" examples:\n") + buf.WriteString(" tenant domain: coretech\n") + buf.WriteString(" provider domain: sports\n") + buf.WriteString(" provider service: hosted\n") + buf.WriteString(" resource group: dev_group\n") + buf.WriteString(" " + domain_example + " show-provider-resource-group-roles sports hosted dev_group\n") + case "add-provider-resource-group-roles": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " add-provider-resource-group-roles provider_domain provider_service resource_group role=action [role=action ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : tenant's domain name\n") + } + buf.WriteString(" provider_domain : provider's domain name\n") + buf.WriteString(" provider_service : provider's service name\n") + buf.WriteString(" resource_group : name of the tenant's resource group\n") + buf.WriteString(" role : name of the role to be added\n") + buf.WriteString(" action : the action value for the role\n") + buf.WriteString(" examples:\n") + buf.WriteString(" tenant domain: coretech\n") + buf.WriteString(" provider domain: sports\n") + buf.WriteString(" provider service: hosted\n") + buf.WriteString(" resource group: dev_group\n") + buf.WriteString(" " + domain_example + " add-provider-resource-group-roles sports hosted dev_group readers=read writers=modify\n") + case "delete-provider-resource-group-roles": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-provider-resource-group-roles provider_domain provider_service resource_group\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : tenant's domain name\n") + } + buf.WriteString(" provider_domain : provider's domain name\n") + buf.WriteString(" provider_service : provider's service name\n") + buf.WriteString(" resource_group : name of the tenant's resource group\n") + buf.WriteString(" examples:\n") + buf.WriteString(" tenant domain: coretech\n") + buf.WriteString(" provider domain: sports\n") + buf.WriteString(" provider service: hosted\n") + buf.WriteString(" resource group: dev_group\n") + buf.WriteString(" " + domain_example + " delete-provider-resource-group-roles sports hosted dev_group\n") + case "get-user-token": + buf.WriteString(" syntax:\n") + buf.WriteString(" get-user-token [authorized_service]\n") + buf.WriteString(" examples:\n") + buf.WriteString(" get-user-token\n") + buf.WriteString(" get-user-token iaas.athenz.api\n") + case "add-profile": + buf.WriteString(" syntax:\n") + buf.WriteString(" add-profile name \n") + buf.WriteString(" examples:\n") + buf.WriteString(" add-profile dev\n") + case "show-profile": + buf.WriteString(" syntax:\n") + buf.WriteString(" show-profile name \n") + buf.WriteString(" examples:\n") + buf.WriteString(" show-profile dev\n") + case "version": + buf.WriteString(" syntax:\n") + buf.WriteString(" version\n") + buf.WriteString(" examples:\n") + buf.WriteString(" version\n") + case "repl": + buf.WriteString(" syntax:\n") + buf.WriteString(" repl\n") + buf.WriteString(" description:\n") + buf.WriteString(" starts zms-cli client in interactive shell mode where the administrator can specify\n") + buf.WriteString(" the domain to be used for all commands using the use-domain command\n") + buf.WriteString(" examples:\n") + buf.WriteString(" repl\n") + buf.WriteString(" > use-domain corecommit\n") + buf.WriteString(" corecommit> list-role\n") + case "system-backup": + buf.WriteString(" syntax:\n") + buf.WriteString(" system-backup dir\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" dir : directory path to store all exported domain's yaml files\n") + buf.WriteString(" : each filename will have the same filename as the domain name\n") + buf.WriteString(" examples:\n") + buf.WriteString(" system-backup /home/athenz/var/backups/zms_data\n") + case "list-server-template": + buf.WriteString(" syntax:\n") + buf.WriteString(" list-server-template\n") + buf.WriteString(" description:\n") + buf.WriteString(" lists the solution templates defined on the server\n") + buf.WriteString(" example:\n") + buf.WriteString(" list-server-template\n") + case "show-server-template": + buf.WriteString(" syntax:\n") + buf.WriteString(" show-server-template template\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" template : solution template name to be displayed\n") + buf.WriteString(" example:\n") + buf.WriteString(" show-server-template vipng\n") + case "list-domain-template": + buf.WriteString(" syntax:\n") + buf.WriteString(" list-domain-template domain\n") + buf.WriteString(" " + domain_param + " list-domain-template\n") + buf.WriteString(" parameters:\n") + buf.WriteString(" domain : retrieve templates applied to this domain\n") + buf.WriteString(" : this argument is required unless -d is specified or\n") + buf.WriteString(" : running in repl/interactive mode with use-domain specified\n") + buf.WriteString(" examples:\n") + buf.WriteString(" list-domain-template coretech.hosted\n") + buf.WriteString(" " + domain_example + " list-domain-template\n") + case "set-domain-template": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " set-domain-template template [template ...]\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain to apply the template to\n") + } + buf.WriteString(" template : name of the template to be applied to the domain\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " set-domain-template vipng\n") + buf.WriteString(" " + domain_example + " set-domain-template vipng cm3\n") + case "delete-domain-template": + buf.WriteString(" syntax:\n") + buf.WriteString(" " + domain_param + " delete-domain-template template\n") + buf.WriteString(" parameters:\n") + if !interactive { + buf.WriteString(" domain : name of the domain to delete the template from\n") + } + buf.WriteString(" template : name of the template to be deleted from the domain\n") + buf.WriteString(" examples:\n") + buf.WriteString(" " + domain_example + " delete-domain-template vipng\n") + default: + if interactive { + buf.WriteString("Unknown command. Type 'help' to see available commands") + } else { + buf.WriteString("Unknown command. Type 'zms-cli help' to see available commands") + } + } + return buf.String() +} + +func (cli Zms) HelpListCommand() string { + var buf bytes.Buffer + buf.WriteString(" Domain commands:\n") + buf.WriteString(" list-domain [prefix] | [limit skip prefix depth]\n") + buf.WriteString(" show-domain [domain]\n") + buf.WriteString(" lookup-domain-by-aws-account account-id\n") + buf.WriteString(" lookup-domain-by-prod-id prod-id\n") + buf.WriteString(" lookup-domain-by-role role-member role-name\n") + buf.WriteString(" add-domain domain prod-id [admin ...] - to add top level domains\n") + buf.WriteString(" add-domain domain [admin ...] - to add sub domains\n") + buf.WriteString(" set-domain-meta description org audit_enabled\n") + buf.WriteString(" set-aws-account account-id\n") + buf.WriteString(" set-prod-id prod-id\n") + buf.WriteString(" import-domain domain [file.yaml [admin ...]] - no file means stdin\n") + buf.WriteString(" export-domain domain [file.yaml] - no file means stdout\n") + buf.WriteString(" update-domain domain [file.yaml] - no file means stdin\n") + buf.WriteString(" delete-domain domain\n") + buf.WriteString(" set-default-admins domain admin [admin ...] (for system administrators only)\n") + buf.WriteString(" get-signed-domains [matching_tag]\n") + buf.WriteString(" use-domain [domain]\n") + buf.WriteString(" check-domain [domain]\n") + buf.WriteString("\n") + buf.WriteString(" Policy commands:\n") + buf.WriteString(" list-policy\n") + buf.WriteString(" show-policy policy\n") + buf.WriteString(" add-policy policy [assertion]\n") + buf.WriteString(" add-assertion policy assertion\n") + buf.WriteString(" delete-assertion policy assertion\n") + buf.WriteString(" delete-policy policy\n") + buf.WriteString(" show-access action resource [alt_identity [trust_domain]]\n") + buf.WriteString(" show-resource principal action\n") + buf.WriteString("\n") + buf.WriteString(" Role commands:\n") + buf.WriteString(" list-role\n") + buf.WriteString(" show-role role [log | expand]\n") + buf.WriteString(" add-delegated-role role trusted_domain\n") + buf.WriteString(" add-group-role role member [member ... ]\n") + buf.WriteString(" add-member group_role user_or_service [user_or_service ...]\n") + buf.WriteString(" check-member group_role user_or_service [user_or_service ...]\n") + buf.WriteString(" delete-member group_role user_or_service [user_or_service ...]\n") + buf.WriteString(" add-provider-role-member provider_service resource_group provider_role user_or_service [user_or_service ...]\n") + buf.WriteString(" show-provider-role-member provider_service resource_group provider_role\n") + buf.WriteString(" delete-provider-role-member provider_service resource_group provider_role user_or_service [user_or_service ...]\n") + buf.WriteString(" delete-role role\n") + buf.WriteString("\n") + buf.WriteString(" Service commands:\n") + buf.WriteString(" list-service\n") + buf.WriteString(" show-service service\n") + buf.WriteString(" add-service service key_id identity_pubkey.pem|identity_key_ybase64\n") + buf.WriteString(" add-provider-service service key_id identity_pubkey.pem|identity_key_ybase64\n") + buf.WriteString(" set-service-endpoint service endpoint\n") + buf.WriteString(" set-service-exe service executable user group\n") + buf.WriteString(" add-service-host service host [host ...]\n") + buf.WriteString(" delete-service-host service host [host ...]\n") + buf.WriteString(" add-public-key service key_id identity_pubkey.pem|identity_key_ybase64\n") + buf.WriteString(" show-public-key service key_id\n") + buf.WriteString(" delete-public-key service key_id\n") + buf.WriteString(" delete-service service\n") + buf.WriteString(" list-host-services host\n") + buf.WriteString("\n") + buf.WriteString(" Entity commands:\n") + buf.WriteString(" list-entity\n") + buf.WriteString(" show-entity entity\n") + buf.WriteString(" add-entity entity key=value [key=value ...]\n") + buf.WriteString(" delete-entity entity\n") + buf.WriteString("\n") + buf.WriteString(" Tenancy commands:\n") + buf.WriteString(" show-tenancy provider\n") + buf.WriteString(" add-tenancy provider\n") + buf.WriteString(" delete-tenancy provider\n") + buf.WriteString(" add-tenancy-resource-group provider resource_group\n") + buf.WriteString(" delete-tenancy-resource-group provider resource-group\n") + buf.WriteString(" show-tenant-roles service tenant_domain\n") + buf.WriteString(" add-tenant-roles service tenant_domain role=action [role=action ...]\n") + buf.WriteString(" delete-tenant-roles service tenant_domain\n") + buf.WriteString(" show-tenant-resource-group-roles service tenant_domain resource_group\n") + buf.WriteString(" add-tenant-resource-group-roles service tenant_domain resource_group role=action [role=action ...]\n") + buf.WriteString(" delete-tenant-resource-group-roles service tenant_domain resource_group\n") + buf.WriteString(" show-provider-resource-group-roles provider_domain provider_service resource_group\n") + buf.WriteString(" add-provider-resource-group-roles provider_domain provider_service resource_group role=action [role=action ...]\n") + buf.WriteString(" delete-provider-resource-group-roles provider_domain provider_service resource_group\n") + buf.WriteString("\n") + buf.WriteString(" Template commands:\n") + buf.WriteString(" list-server-template\n") + buf.WriteString(" list-domain-template\n") + buf.WriteString(" show-server-template template\n") + buf.WriteString(" set-domain-template template [template]\n") + buf.WriteString(" delete-domain-template template\n") + buf.WriteString("\n") + buf.WriteString(" Other commands:\n") + buf.WriteString(" get-user-token [authorized_service]\n") + buf.WriteString(" add-profile name\n") + buf.WriteString(" show-profile name\n") + buf.WriteString(" repl\n") + buf.WriteString(" version\n") + buf.WriteString("\n") + return buf.String() +} + +func (cli Zms) ybase64(data []byte) string { + b64 := base64.StdEncoding.EncodeToString(data) + yb64 := strings.Replace(strings.Replace(strings.Replace(b64, "+", ".", -1), "/", "_", -1), "=", "-", -1) + return yb64 +} + +func (cli Zms) getPublicKey(s string) (*string, error) { + if strings.HasSuffix(s, ".pem") || strings.HasSuffix(s, ".key") { + bytes, err := ioutil.ReadFile(s) + if err != nil { + return nil, err + } + yb64 := cli.ybase64(bytes) + return &yb64, nil + } else { + return &s, nil + } +} diff --git a/libs/go/zmscli/domain.go b/libs/go/zmscli/domain.go new file mode 100644 index 00000000000..0b8cf648f1b --- /dev/null +++ b/libs/go/zmscli/domain.go @@ -0,0 +1,480 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "strconv" + "strings" + + "../../../clients/go/zms" + "gopkg.in/yaml.v2" +) + +func (cli Zms) DeleteDomain(dn string) (*string, error) { + _, err := cli.Zms.GetDomain(zms.DomainName(dn)) + if err == nil { + i := strings.LastIndex(dn, ".") + name := dn + parent := "" + if i < 0 { + err = cli.Zms.DeleteTopLevelDomain(zms.DomainName(dn), cli.AuditRef) + } else { + parent = dn[0:i] + name = dn[i+1:] + // special case for top level user domains + // where parent is just the user domain + if parent == cli.UserDomain { + err = cli.Zms.DeleteUserDomain(zms.SimpleName(name), cli.AuditRef) + } else { + err = cli.Zms.DeleteSubDomain(zms.DomainName(parent), zms.DomainName(name), cli.AuditRef) + } + } + if err == nil { + s := "[Deleted domain " + dn + "]" + return &s, nil + } + } + return nil, err +} + +func (cli Zms) ImportDomain(dn string, filename string, admins []string) (*string, error) { + validatedAdmins := cli.validatedUsers(admins, true) + var spec map[string]interface{} + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + err = yaml.Unmarshal(data, &spec) + if err != nil { + return nil, err + } + dnSpec := spec["domain"].(map[interface{}]interface{}) + dn2 := dnSpec["name"].(string) + if dn2 != dn { + return nil, fmt.Errorf("Domain name mismatch. Expected " + dn + ", encountered " + dn2) + } + var productId int + if val, ok := dnSpec["product_id"]; ok { + productId = val.(int) + } else if strings.LastIndex(dn, ".") < 0 { + return nil, fmt.Errorf("Top Level Domains require an integer number specified for the Product ID") + } + productId32 := int32(productId) + _, err = cli.AddDomain(dn, &productId32, admins) + if err != nil { + return nil, err + } + var descr string + if val, ok := dnSpec["description"]; ok { + switch val.(type) { + case int: + descr = strconv.Itoa(val.(int)) + case string: + descr = val.(string) + } + } + var org string + if val, ok := dnSpec["org"]; ok { + org = val.(string) + } + var auditEnabled bool + if val, ok := dnSpec["audit_enabled"]; ok { + auditEnabled = val.(bool) + } + var account string + if val, ok := dnSpec["aws_account"]; ok { + switch val.(type) { + case int: + account = strconv.Itoa(val.(int)) + case string: + account = val.(string) + } + } + err = cli.SetCompleteDomainMeta(dn, descr, org, auditEnabled, account, productId32) + if err != nil { + return nil, err + } + lstRoles := dnSpec["roles"].([]interface{}) + err = cli.importRoles(dn, lstRoles, validatedAdmins, false) + if err != nil { + return nil, err + } + lstPolicies := dnSpec["policies"].([]interface{}) + err = cli.importPolicies(dn, lstPolicies, false) + if err != nil { + return nil, err + } + if lstServices, ok := dnSpec["services"].([]interface{}); ok { + err = cli.importServices(dn, lstServices, false) + if err != nil { + return nil, err + } + } + s := "[imported domain '" + dn + "' successfully]" + return &s, nil +} + +func (cli Zms) UpdateDomain(dn string, filename string) (*string, error) { + var spec map[string]interface{} + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + err = yaml.Unmarshal(data, &spec) + if err != nil { + return nil, err + } + dnSpec := spec["domain"].(map[interface{}]interface{}) + dn2 := dnSpec["name"].(string) + if dn2 != dn { + return nil, fmt.Errorf("Domain name mismatch. Expected " + dn + ", encountered " + dn2) + } + var descr string + if val, ok := dnSpec["description"]; ok { + switch val.(type) { + case int: + descr = strconv.Itoa(val.(int)) + case string: + descr = val.(string) + } + } + var org string + if val, ok := dnSpec["org"]; ok { + org = val.(string) + } + var auditEnabled bool + if val, ok := dnSpec["audit_enabled"]; ok { + auditEnabled = val.(bool) + } + var account string + if val, ok := dnSpec["aws_account"]; ok { + switch val.(type) { + case int: + account = strconv.Itoa(val.(int)) + case string: + account = val.(string) + } + } + var productId int + if val, ok := dnSpec["product_id"]; ok { + productId = val.(int) + } + productId32 := int32(productId) + err = cli.SetCompleteDomainMeta(dn, descr, org, auditEnabled, account, productId32) + if err != nil { + return nil, err + } + if lstRoles, ok := dnSpec["roles"].([]interface{}); ok { + err = cli.importRoles(dn, lstRoles, nil, true) + if err != nil { + return nil, err + } + } + if lstPolicies, ok := dnSpec["policies"].([]interface{}); ok { + err = cli.importPolicies(dn, lstPolicies, true) + if err != nil { + return nil, err + } + } + if lstServices, ok := dnSpec["services"].([]interface{}); ok { + err = cli.importServices(dn, lstServices, true) + if err != nil { + return nil, err + } + } + s := "[updated domain '" + dn + "' successfully]" + return &s, nil +} + +func (cli Zms) ExportDomain(dn string, filename string) (*string, error) { + verbose := cli.Verbose + cli.Verbose = false + data, err := cli.showDomain(dn, true) + cli.Verbose = verbose + if err == nil && data != nil { + s := *data + if filename == "-" { + fmt.Println(s) + } else { + err = ioutil.WriteFile(filename, []byte(s), 0644) + } + } + return nil, err +} + +func (cli Zms) SystemBackup(dir string) (*string, error) { + res, err := cli.Zms.GetDomainList(nil, "", "", nil, "", nil, "", "", "") + if err != nil { + return nil, err + } + verbose := cli.Verbose + cli.Verbose = false + for _, name := range res.Names { + fmt.Fprintf(os.Stdout, "Processing domain "+string(name)+"...\n") + filename := dir + "/" + string(name) + data, err := cli.showDomain(string(name), true) + if err == nil && data != nil { + s := *data + err = ioutil.WriteFile(filename, []byte(s), 0644) + } + } + cli.Verbose = verbose + s := "[exported " + strconv.Itoa(len(res.Names)) + " domains to " + dir + " directory]" + return &s, nil +} + +func (cli Zms) AddDomain(dn string, productId *int32, admins []string) (*string, error) { + // sanity check cli usage: sub domain admin list should not contain a productId + if productId == nil && admins != nil && len(admins) > 0 { + // just checking the first admin to decide if productId was actually added + _, err := cli.getInt32(admins[0]) + if err == nil { + s := "Do not specify Product ID when creating a sub domain. Only top level domains require a Product ID. Bad value: " + admins[0] + return nil, fmt.Errorf(s) + } + } + validatedAdmins := cli.validatedUsers(admins, true) + s, err := cli.createDomain(dn, productId, validatedAdmins) + if err != nil { + return nil, err + } + return s, nil +} + +func (cli Zms) createDomain(dn string, productId *int32, admins []string) (*string, error) { + i := strings.LastIndex(dn, ".") + name := dn + parent := "" + if i < 0 { + tld := zms.TopLevelDomain{} + tld.Name = zms.SimpleName(dn) + tld.AdminUsers = cli.createResourceList(admins) + tld.YpmId = productId + _, err := cli.Zms.PostTopLevelDomain(cli.AuditRef, &tld) + if err == nil { + s := "[domain created: " + dn + "]" + return &s, nil + } + return nil, err + } else { + parent = dn[0:i] + name = dn[i+1:] + // special case for top level user domains + // where parent is just the user domain + if parent == cli.UserDomain { + d := zms.UserDomain{} + d.Name = zms.SimpleName(name) + _, err := cli.Zms.PostUserDomain(zms.SimpleName(name), cli.AuditRef, &d) + if err == nil { + s := "[domain created: " + dn + "]" + return &s, nil + } + return nil, err + } else { + d := zms.SubDomain{} + d.Name = zms.SimpleName(name) + d.Parent = zms.DomainName(parent) + d.AdminUsers = cli.createResourceList(admins) + _, err := cli.Zms.PostSubDomain(zms.DomainName(parent), cli.AuditRef, &d) + if err == nil { + s := "[subdomain created: " + dn + "]" + return &s, nil + } + return nil, err + } + } +} + +func (cli Zms) LookupDomainByRole(roleMember string, roleName string) (*string, error) { + var buf bytes.Buffer + res, err := cli.Zms.GetDomainList(nil, "", "", nil, "", nil, zms.ResourceName(roleMember), zms.ResourceName(roleName), "") + if err == nil { + buf.WriteString("domains:\n") + for _, name := range res.Names { + buf.WriteString(indent_level1_dash + string(name) + "\n") + } + s := buf.String() + return &s, nil + } + return nil, err +} + +func (cli Zms) LookupDomainById(account string, productId *int32) (*string, error) { + var buf bytes.Buffer + res, err := cli.Zms.GetDomainList(nil, "", "", nil, account, productId, "", "", "") + if err == nil { + buf.WriteString("domain:\n") + for _, name := range res.Names { + buf.WriteString(indent_level1_dash + string(name) + "\n") + } + s := buf.String() + return &s, nil + } + return nil, err +} + +func (cli Zms) ListDomains(limit *int32, skip string, prefix string, depth *int32) (*string, error) { + var buf bytes.Buffer + fmt.Printf("Our url: " + cli.ZmsUrl + " : " + cli.Zms.URL + "\n") + res, err := cli.Zms.GetDomainList(limit, skip, prefix, depth, "", nil, "", "", "") + if err == nil { + buf.WriteString("domains:\n") + for _, name := range res.Names { + buf.WriteString(indent_level1_dash + string(name) + "\n") + } + s := buf.String() + return &s, nil + } + return nil, err +} + +func (cli Zms) GetSignedDomains(dn string, matchingTag string) (*string, error) { + var buf bytes.Buffer + res, etag, err := cli.Zms.GetSignedDomains(zms.DomainName(dn), "false", matchingTag) + if err != nil { + return nil, err + } + buf.WriteString("ETag: " + etag + "\n") + if res != nil { + for _, domain := range res.Domains { + cli.dumpSignedDomain(&buf, domain, false) + } + } + s := buf.String() + return &s, nil +} + +func (cli Zms) ShowDomain(dn string) (*string, error) { + + domain, err := cli.Zms.GetDomain(zms.DomainName(dn)) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + cli.dumpDomain(&buf, domain) + + // now retrieve the full domain in one call + res, _, err := cli.Zms.GetSignedDomains(zms.DomainName(dn), "false", "") + if err != nil { + return nil, err + } + + // make sure we have a domain and it must be only one + if res != nil && len(res.Domains) == 1 { + cli.dumpSignedDomain(&buf, res.Domains[0], true) + } + s := buf.String() + return &s, nil +} + +func (cli Zms) CheckDomain(dn string) (*string, error) { + domainDataCheck, err := cli.Zms.GetDomainDataCheck(zms.DomainName(dn)) + if err != nil { + return nil, err + } + var buf bytes.Buffer + buf.WriteString("checked data:\n") + cli.dumpDataCheck(&buf, *domainDataCheck) + s := buf.String() + return &s, nil +} + +func (cli Zms) showDomain(dn string, export bool) (*string, error) { + + domain, err := cli.Zms.GetDomain(zms.DomainName(dn)) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + cli.dumpDomain(&buf, domain) + cli.dumpRoles(&buf, dn) + cli.dumpPolicies(&buf, dn) + cli.dumpServices(&buf, dn) + + var names []string + names, err = cli.entityNames(dn) + if err != nil { + return nil, err + } + cli.dumpEntities(&buf, dn, names) + + s := buf.String() + return &s, nil +} + +func (cli Zms) SetCompleteDomainMeta(dn string, descr string, org string, auditEnabled bool, account string, productId int32) error { + meta := zms.DomainMeta{ + Description: descr, + Org: zms.ResourceName(org), + Enabled: nil, + AuditEnabled: &auditEnabled, + Account: account, + YpmId: &productId, + } + return cli.Zms.PutDomainMeta(zms.DomainName(dn), cli.AuditRef, &meta) +} + +func (cli Zms) SetDomainMeta(dn string, descr string, org string, auditEnabled bool) (*string, error) { + domain, err := cli.Zms.GetDomain(zms.DomainName(dn)) + if err != nil { + return nil, err + } + meta := zms.DomainMeta{ + Description: descr, + Org: zms.ResourceName(org), + Enabled: domain.Enabled, + AuditEnabled: &auditEnabled, + Account: domain.Account, + YpmId: domain.YpmId, + } + err = cli.Zms.PutDomainMeta(zms.DomainName(dn), cli.AuditRef, &meta) + if err != nil { + return nil, err + } + s := "[domain " + dn + " metadata successfully updated]\n" + return &s, nil +} + +func (cli Zms) SetDomainAccount(dn string, account string) (*string, error) { + domain, err := cli.Zms.GetDomain(zms.DomainName(dn)) + if err != nil { + return nil, err + } + err = cli.SetCompleteDomainMeta(dn, domain.Description, string(domain.Org), *domain.AuditEnabled, account, *domain.YpmId) + if err != nil { + return nil, err + } + s := "[domain " + dn + " account successfully updated]\n" + return &s, nil +} + +func (cli Zms) SetDomainProductId(dn string, productId int32) (*string, error) { + domain, err := cli.Zms.GetDomain(zms.DomainName(dn)) + if err != nil { + return nil, err + } + err = cli.SetCompleteDomainMeta(dn, domain.Description, string(domain.Org), *domain.AuditEnabled, domain.Account, productId) + if err != nil { + return nil, err + } + s := "[domain " + dn + " product-id successfully updated]\n" + return &s, nil +} + +func (cli Zms) SetDefaultAdmins(dn string, admins []string) (*string, error) { + validatedAdmins := cli.createResourceList(cli.validatedUsers(admins, false)) + defaultAdmins := zms.DefaultAdmins{Admins: validatedAdmins} + err := cli.Zms.PutDefaultAdmins(zms.DomainName(dn), cli.AuditRef, &defaultAdmins) + if err != nil { + return nil, err + } + s := "[domain " + dn + " administrators successfully set]\n" + return &s, nil +} diff --git a/libs/go/zmscli/dump.go b/libs/go/zmscli/dump.go new file mode 100644 index 00000000000..71382b828e4 --- /dev/null +++ b/libs/go/zmscli/dump.go @@ -0,0 +1,449 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + "log" + "strconv" + "strings" + + "../../../clients/go/zms" +) + +var ( + indent_level1 string = " " + indent_level1_dash string = " - " + indent_level1_dash_lvl string = " " + indent_level2 string = " " + indent_level2_dash string = " - " + indent_level2_dash_lvl string = " " + indent_level3 string = " " +) + +func (cli Zms) dumpDomain(buf *bytes.Buffer, domain *zms.Domain) { + buf.WriteString("domain:\n") + + buf.WriteString(indent_level1) + buf.WriteString("name: ") + buf.WriteString(string(domain.Name)) + buf.WriteString("\n") + + if domain.Description != "" { + buf.WriteString(indent_level1) + buf.WriteString("description: ") + buf.WriteString(domain.Description) + buf.WriteString("\n") + } + if domain.Account != "" { + buf.WriteString(indent_level1) + buf.WriteString("aws_account: ") + buf.WriteString(domain.Account) + buf.WriteString("\n") + } + productId := int(*domain.YpmId) + if productId != 0 { + buf.WriteString(indent_level1) + buf.WriteString("product_id: ") + buf.WriteString(strconv.Itoa(productId)) + buf.WriteString("\n") + } + if domain.Org != "" { + buf.WriteString(indent_level1) + buf.WriteString("org: ") + buf.WriteString(string(domain.Org)) + buf.WriteString("\n") + } + if domain.AuditEnabled != nil { + buf.WriteString(indent_level1) + buf.WriteString("audit_enabled: ") + buf.WriteString(strconv.FormatBool(*domain.AuditEnabled)) + buf.WriteString("\n") + } +} + +func (cli Zms) dumpDataCheck(buf *bytes.Buffer, dataCheck zms.DomainDataCheck) { + if len(dataCheck.DanglingRoles) != 0 { + buf.WriteString(indent_level1) + buf.WriteString("Roles not referenced in any assertion or empty: \n") + for _, item := range dataCheck.DanglingRoles { + buf.WriteString(indent_level2 + string(item) + "\n") + } + } + if dataCheck.DanglingPolicies != nil { + buf.WriteString(indent_level1) + buf.WriteString("Policies where assertion is referencing a non-existent role: \n") + for _, item := range dataCheck.DanglingPolicies { + buf.WriteString(indent_level2 + "policy: " + string(item.PolicyName) + " role: " + string(item.RoleName) + "\n") + } + } + buf.WriteString(indent_level1) + buf.WriteString("Number of policies: " + strconv.Itoa(int(dataCheck.PolicyCount)) + "\n") + buf.WriteString(indent_level1) + buf.WriteString("Number of assertions: " + strconv.Itoa(int(dataCheck.AssertionCount)) + "\n") + if len(dataCheck.ProvidersWithoutTrust) != 0 { + buf.WriteString(indent_level1) + buf.WriteString("On-boarded providers without trust relationship: \n") + for _, item := range dataCheck.ProvidersWithoutTrust { + buf.WriteString(indent_level2 + string(item) + "\n") + } + } + if len(dataCheck.TenantsWithoutAssumeRole) != 0 { + buf.WriteString(indent_level1) + buf.WriteString("On-boarded tenants without assume_role assertions: \n") + for _, item := range dataCheck.TenantsWithoutAssumeRole { + buf.WriteString(indent_level2 + string(item) + "\n") + } + } +} + +func (cli Zms) displayObjectName(buf *bytes.Buffer, yrn string, objType string, indent1 string) { + buf.WriteString(indent1) + buf.WriteString("name: ") + if cli.Verbose || objType == "" { + buf.WriteString(yrn) + } else { + buf.WriteString(localName(yrn, objType)) + } + buf.WriteString("\n") +} + +func (cli Zms) dumpRole(buf *bytes.Buffer, role zms.Role, auditLog bool, indent1 string, indent2 string) { + cli.displayObjectName(buf, string(role.Name), ":role.", indent1) + if role.Members != nil && len(role.Members) > 0 { + buf.WriteString(indent2) + buf.WriteString("members: [") + cli.dumpUserName(buf, strings.Join(cli.createStringList(role.Members), ", "), true) + buf.WriteString("]\n") + if auditLog { + buf.WriteString(indent2) + buf.WriteString("changes: \n") + indent3_dash := indent2 + " - " + indent3_dash_lvl := indent2 + " " + for _, logItem := range role.AuditLog { + buf.WriteString(indent3_dash + "Action: " + logItem.Action + "\n") + buf.WriteString(indent3_dash_lvl + "Admin: " + string(logItem.Admin) + "\n") + buf.WriteString(indent3_dash_lvl + "Member: " + string(logItem.Member) + "\n") + buf.WriteString(indent3_dash_lvl + "Date: " + logItem.Created.String() + "\n") + buf.WriteString(indent3_dash_lvl + "Ref: " + logItem.AuditRef + "\n") + } + } + } + if role.Trust != "" { + buf.WriteString(indent2) + buf.WriteString("trust: ") + buf.WriteString(string(role.Trust)) + buf.WriteString("\n") + } +} + +func (cli Zms) dumpRoles(buf *bytes.Buffer, dn string) { + buf.WriteString(indent_level1) + buf.WriteString("roles:\n") + members := true + roles, err := cli.Zms.GetRoles(zms.DomainName(dn), &members) + if err != nil { + log.Fatalf("Unable to get role list - error: %v", err) + } + for _, role := range roles.List { + cli.dumpRole(buf, *role, false, indent_level2_dash, indent_level2_dash_lvl) + } +} + +func localName(yrn string, prefix string) string { + idx := strings.Index(yrn, prefix) + s := yrn + if idx != -1 { + s = yrn[idx+len(prefix):] + } + return s +} + +func (cli Zms) dumpAssertion(buf *bytes.Buffer, assertion *zms.Assertion, dn string, indent1 string) { + showYrn := cli.Verbose + buf.WriteString(indent1) + effect := "grant" + if assertion.Effect != nil { + effect = strings.ToLower(assertion.Effect.String()) + if effect == "allow" { + effect = "grant" + } + } + buf.WriteString(effect) + buf.WriteString(" ") + buf.WriteString(assertion.Action) + buf.WriteString(" to ") + if showYrn { + buf.WriteString(assertion.Role) + } else { + buf.WriteString(localName(assertion.Role, ":role.")) + } + buf.WriteString(" on ") + if showYrn { + buf.WriteString(assertion.Resource) + } else { + prefix := dn + ":" + if strings.HasPrefix(assertion.Resource, prefix) { + buf.WriteString(assertion.Resource[len(prefix):]) + } else { + buf.WriteString(assertion.Resource) + } + } + buf.WriteString("\n") +} + +func (cli Zms) dumpPolicy(buf *bytes.Buffer, policy zms.Policy, indent1 string, indent2 string) { + yrn := string(policy.Name) + cli.displayObjectName(buf, yrn, ":policy.", indent1) + dn := yrn[0:strings.LastIndex(yrn, ":")] + buf.WriteString(indent2) + if len(policy.Assertions) == 0 { + buf.WriteString("assertions: []\n") + } else { + buf.WriteString("assertions:\n") + indent3 := indent2 + " - " + for _, assertion := range policy.Assertions { + cli.dumpAssertion(buf, assertion, dn, indent3) + } + } +} + +func (cli Zms) dumpPolicies(buf *bytes.Buffer, dn string) { + buf.WriteString(indent_level1) + buf.WriteString("policies:\n") + assertions := true + policies, err := cli.Zms.GetPolicies(zms.DomainName(dn), &assertions) + if err != nil { + log.Fatalf("Unable to get policy list - error: %v", err) + } + for _, policy := range policies.List { + cli.dumpPolicy(buf, *policy, indent_level2_dash, indent_level2_dash_lvl) + } +} + +func (cli Zms) dumpEntities(buf *bytes.Buffer, dn string, entitynames []string) { + if len(entitynames) > 0 { + buf.WriteString(indent_level1) + buf.WriteString("entities:\n") + for _, en := range entitynames { + entity, err := cli.Zms.GetEntity(zms.DomainName(dn), zms.EntityName(en)) + if err != nil { + return + } + cli.dumpEntity(buf, *entity, indent_level2_dash, indent_level2_dash_lvl) + } + } +} + +func (cli Zms) dumpEntity(buf *bytes.Buffer, entity zms.Entity, indent1 string, indent2 string) { + cli.displayObjectName(buf, string(entity.Name), "", indent1) + buf.WriteString(indent2) + buf.WriteString("value:\n") + indent3_dash := indent2 + " - " + indent3_dash_lvl := indent2 + " " + for key, data := range entity.Value { + buf.WriteString(indent3_dash + "key: " + string(key) + "\n") + buf.WriteString(indent3_dash_lvl + "data: " + data.(string) + "\n") + } +} + +func (cli Zms) dumpService(buf *bytes.Buffer, svc zms.ServiceIdentity, indent1 string, indent2 string) { + cli.displayObjectName(buf, string(svc.Name), "", indent1) + if svc.Modified != nil { + buf.WriteString(indent2) + buf.WriteString("modified: ") + buf.WriteString(svc.Modified.String()) + buf.WriteString("\n") + } + if svc.Executable != "" { + buf.WriteString(indent2) + buf.WriteString("executable: ") + buf.WriteString(svc.Executable) + buf.WriteString("\n") + } + if svc.User != "" { + buf.WriteString(indent2) + buf.WriteString("user: ") + buf.WriteString(svc.User) + buf.WriteString("\n") + } + if svc.Group != "" { + buf.WriteString(indent2) + buf.WriteString("group: ") + buf.WriteString(svc.Group) + buf.WriteString("\n") + } + if svc.ProviderEndpoint != "" { + buf.WriteString(indent2) + buf.WriteString("providerEndpoint: ") + buf.WriteString(svc.ProviderEndpoint) + buf.WriteString("\n") + } + if svc.Hosts != nil { + buf.WriteString(indent2) + buf.WriteString("hosts: [") + buf.WriteString(strings.Join(svc.Hosts, ", ")) + buf.WriteString("]\n") + } + buf.WriteString(indent2) + if len(svc.PublicKeys) == 0 { + buf.WriteString("publicKeys: []\n") + } else { + buf.WriteString("publicKeys: \n") + for _, publicKey := range svc.PublicKeys { + buf.WriteString(indent2 + " - keyId: " + publicKey.Id + "\n") + buf.WriteString(indent2 + " value: " + publicKey.Key + "\n") + } + } +} + +func (cli Zms) dumpObjectList(buf *bytes.Buffer, list []string, dn string, object string) { + for _, item := range list { + if cli.Verbose { + buf.WriteString(indent_level1_dash + dn + ":" + object + "." + item + "\n") + } else { + buf.WriteString(indent_level1_dash + item + "\n") + } + } +} + +func (cli Zms) dumpServices(buf *bytes.Buffer, dn string) { + publickeys := true + hosts := true + services, err := cli.Zms.GetServiceIdentities(zms.DomainName(dn), &publickeys, &hosts) + if err != nil { + log.Fatalf("Unable to get service list - error: %v", err) + } + if len(services.List) > 0 { + buf.WriteString(indent_level1) + buf.WriteString("services:\n") + for _, service := range services.List { + cli.dumpService(buf, *service, indent_level2_dash, indent_level2_dash_lvl) + } + } +} + +func (cli Zms) dumpMultilineString(buf *bytes.Buffer, s string, indent string) { + lst := strings.Split(s, "\n") + if len(lst) > 0 { + buf.WriteString("\n") + for _, ss := range lst { + buf.WriteString(indent) + buf.WriteString(ss) + buf.WriteString("\n") + } + } else { + buf.WriteString(s) + } +} + +func (cli Zms) dumpTenancy(buf *bytes.Buffer, tenancy *zms.Tenancy, indent string) { + buf.WriteString(indent + "tenant: " + string(tenancy.Domain) + "\n") + buf.WriteString(indent + "provider: " + string(tenancy.Service) + "\n") + for _, resourceGroup := range tenancy.ResourceGroups { + buf.WriteString(indent + "resource-group: " + string(resourceGroup) + "\n") + } +} + +func (cli Zms) dumpTenantRoles(buf *bytes.Buffer, tenantRoles *zms.TenantRoles, indent1 string, indent2 string) { + for _, roleAction := range tenantRoles.Roles { + buf.WriteString(indent1 + "role: " + string(roleAction.Role) + "\n") + buf.WriteString(indent2 + "action: " + roleAction.Action + "\n") + } +} + +func (cli Zms) dumpTenantResourceGroupRoles(buf *bytes.Buffer, tenantRoles *zms.TenantResourceGroupRoles, indent1 string, indent2 string) { + buf.WriteString(indent1 + "name: " + string(tenantRoles.ResourceGroup) + "\n") + buf.WriteString(indent2 + "tenant-roles:\n") + indent3_dash := indent2 + " - " + indent3_dash_lvl := indent2 + " " + for _, roleAction := range tenantRoles.Roles { + buf.WriteString(indent3_dash + "role: " + string(roleAction.Role) + "\n") + buf.WriteString(indent3_dash_lvl + "action: " + roleAction.Action + "\n") + } +} + +func (cli Zms) dumpProviderResourceGroupRoles(buf *bytes.Buffer, providerRoles *zms.ProviderResourceGroupRoles, indent1 string, indent2 string) { + buf.WriteString(indent1 + "name: " + string(providerRoles.ResourceGroup) + "\n") + buf.WriteString(indent2 + "provider-roles:\n") + for _, roleAction := range providerRoles.Roles { + buf.WriteString(indent2 + " - " + string(roleAction.Role) + "\n") + } +} + +func (cli Zms) dumpUserName(buf *bytes.Buffer, user string, showYrn bool) { + if showYrn { + buf.WriteString(user) + } else { + buf.WriteString(strings.Replace(user, cli.UserDomain+".", "", -1)) + } +} + +func (cli Zms) dumpRoleMembership(buf *bytes.Buffer, member zms.Membership) { + buf.WriteString(indent_level1_dash + "member: ") + cli.dumpUserName(buf, string(member.MemberName), true) + buf.WriteString("\n") + buf.WriteString(indent_level1_dash_lvl + "result: " + strconv.FormatBool(*member.IsMember) + "\n") +} + +func (cli Zms) dumpSignedDomain(buf *bytes.Buffer, signedDomain *zms.SignedDomain, showDomain bool) { + + domainData := signedDomain.Domain + if !showDomain { + buf.WriteString("domain: ") + buf.WriteString("\n") + buf.WriteString(indent_level1) + buf.WriteString("name: ") + buf.WriteString(string(domainData.Name)) + buf.WriteString("\n") + if domainData.Account != "" { + buf.WriteString(indent_level1) + buf.WriteString("account: ") + buf.WriteString(domainData.Account) + buf.WriteString("\n") + } + buf.WriteString(indent_level1) + buf.WriteString("signature: ") + buf.WriteString(signedDomain.Signature) + buf.WriteString("\n") + buf.WriteString(indent_level1) + buf.WriteString("keyId: ") + buf.WriteString(signedDomain.KeyId) + buf.WriteString("\n") + } + buf.WriteString(indent_level1) + buf.WriteString("modified: ") + buf.WriteString(domainData.Modified.String()) + buf.WriteString("\n") + + buf.WriteString(indent_level1) + buf.WriteString("roles:\n") + for _, role := range domainData.Roles { + cli.dumpRole(buf, *role, false, indent_level2_dash, indent_level2_dash_lvl) + } + + buf.WriteString(indent_level1) + buf.WriteString("policies:\n") + signedPolicies := domainData.Policies + domainPolicies := signedPolicies.Contents + for _, policy := range domainPolicies.Policies { + cli.dumpPolicy(buf, *policy, indent_level2_dash, indent_level2_dash_lvl) + } + + if len(domainData.Services) > 0 { + buf.WriteString(indent_level1) + buf.WriteString("services:\n") + for _, service := range domainData.Services { + cli.dumpService(buf, *service, indent_level2_dash, indent_level2_dash_lvl) + } + } +} + +func (cli Zms) dumpProfile(buf *bytes.Buffer, name, content string) { + buf.WriteString("profile:\n") + buf.WriteString(indent_level1) + buf.WriteString("name: ") + buf.WriteString(name) + cli.dumpMultilineString(buf, content, indent_level1) +} diff --git a/libs/go/zmscli/dump_test.go b/libs/go/zmscli/dump_test.go new file mode 100644 index 00000000000..17ed03732fa --- /dev/null +++ b/libs/go/zmscli/dump_test.go @@ -0,0 +1,60 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + "testing" +) + +func TestLocalName(t *testing.T) { + + local := localName("coretech:role.role1", ":role.") + if local != "role1" { + t.Error("coretech:role.role1 didn't return role1 with prefix :role.") + } + + local = localName("coretech:role.role1.role2", ":role.") + if local != "role1.role2" { + t.Error("coretech:role.role1.role2 didn't return role1.role2 with prefix :role.") + } + + local = localName("coretech.service1", ":service.") + if local != "coretech.service1" { + t.Error("coretech.service1 didn't return service1 with prefix :service.") + } +} + +func TestDisplayObjectName(t *testing.T) { + + var buf bytes.Buffer + zms := Zms{} + + zms.verbose = false + zms.displayObjectName(&buf, "coretech:role.role1", ":role.", "--") + if buf.String() != "--name: role1\n" { + t.Error("coretech:role.role1 didn't display the correct object name with verbose off") + } + + buf.Reset() + zms.verbose = true + zms.displayObjectName(&buf, "coretech:role.role1", ":role.", "--") + if buf.String() != "--name: coretech:role.role1\n" { + t.Error("coretech:role.role1 didn't display the correct object name with verbose on") + } + + buf.Reset() + zms.verbose = false + zms.displayObjectName(&buf, "coretech:role.role1", "", "--") + if buf.String() != "--name: coretech:role.role1\n" { + t.Error("coretech:role.role1 didn't display the correct object name with empty obj type") + } + + buf.Reset() + zms.verbose = false + zms.displayObjectName(&buf, "coretech:role.role1", ":policy.", "--") + if buf.String() != "--name: coretech:role.role1\n" { + t.Error("coretech:role.role1 didn't display the correct object name with no match obj type") + } +} diff --git a/libs/go/zmscli/entity.go b/libs/go/zmscli/entity.go new file mode 100644 index 00000000000..d1a9ec02573 --- /dev/null +++ b/libs/go/zmscli/entity.go @@ -0,0 +1,76 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + "strings" + + "github.com/ardielle/ardielle-go/rdl" + "../../../clients/go/zms" +) + +func (cli Zms) ShowEntity(dn string, en string) (*string, error) { + entity, err := cli.Zms.GetEntity(zms.DomainName(dn), zms.EntityName(en)) + if err != nil { + return nil, err + } + var buf bytes.Buffer + buf.WriteString("entity:\n") + cli.dumpEntity(&buf, *entity, indent_level1_dash, indent_level1_dash_lvl) + s := buf.String() + return &s, nil +} + +func (cli Zms) AddEntity(dn string, en string, values []string) (*string, error) { + entityValue := make(map[rdl.Symbol]interface{}) + for _, item := range values { + tokens := strings.Split(item, "=") + if len(tokens) == 2 { + entityValue[rdl.Symbol(tokens[0])] = tokens[1] + } + } + var entity zms.Entity + entity.Name = zms.EntityName(en) + entity.Value = entityValue + err := cli.Zms.PutEntity(zms.DomainName(dn), zms.EntityName(en), cli.AuditRef, &entity) + if err != nil { + return nil, err + } + return cli.ShowEntity(dn, en) +} + +func (cli Zms) DeleteEntity(dn string, en string) (*string, error) { + err := cli.Zms.DeleteEntity(zms.DomainName(dn), zms.EntityName(en), cli.AuditRef) + if err != nil { + return nil, err + } else { + s := "[Deleted entity: " + dn + "." + en + "]" + return &s, nil + } +} + +func (cli Zms) entityNames(dn string) ([]string, error) { + entities := make([]string, 0) + lst, err := cli.Zms.GetEntityList(zms.DomainName(dn)) + if err != nil { + return nil, err + } + for _, n := range lst.Names { + entities = append(entities, string(n)) + } + return entities, nil +} + +func (cli Zms) ListEntities(dn string) (*string, error) { + var buf bytes.Buffer + entities, err := cli.entityNames(dn) + if err != nil { + return nil, err + } + buf.WriteString("entities:\n") + cli.dumpObjectList(&buf, entities, dn, "entity") + s := buf.String() + return &s, nil +} diff --git a/libs/go/zmscli/import.go b/libs/go/zmscli/import.go new file mode 100644 index 00000000000..bc050aa5b12 --- /dev/null +++ b/libs/go/zmscli/import.go @@ -0,0 +1,212 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "fmt" + "os" + "strconv" + "strings" + + "../../../clients/go/zms" +) + +func (cli Zms) importRoles(dn string, lstRoles []interface{}, validatedAdmins []string, skipErrors bool) error { + for _, role := range lstRoles { + roleMap := role.(map[interface{}]interface{}) + rn := roleMap["name"].(string) + fmt.Fprintf(os.Stdout, "Processing role "+rn+"...\n") + if val, ok := roleMap["members"]; ok { + mem := val.([]interface{}) + members := make([]string, 0) + var err error + if rn == "admin" && validatedAdmins != nil { + // need to retrieve the current admin role + // and make sure to remove any existing admin + role, err := cli.Zms.GetRole(zms.DomainName(dn), "admin", nil, nil) + if err != nil { + return err + } + roleMembers := cli.createStringList(role.Members) + for _, m := range mem { + if !cli.contains(roleMembers, m.(string)) { + members = append(members, m.(string)) + } + } + for _, a := range validatedAdmins { + if !cli.contains(members, a) && !cli.contains(roleMembers, a) { + members = append(members, a) + } + } + _, err = cli.AddMembers(dn, rn, members) + } else { + for _, m := range mem { + members = append(members, m.(string)) + } + b := cli.Verbose + cli.Verbose = true + _, err = cli.AddGroupRole(dn, rn, members) + cli.Verbose = b + } + if err != nil { + if skipErrors { + fmt.Println("***", err) + } else { + return err + } + } + } else if val, ok := roleMap["trust"]; ok { + trust := val.(string) + _, err := cli.AddDelegatedRole(dn, rn, trust) + if err != nil { + if skipErrors { + fmt.Println("***", err) + } else { + return err + } + } + } else { + members := make([]string, 0) + b := cli.Verbose + cli.Verbose = true + _, err := cli.AddGroupRole(dn, rn, members) + cli.Verbose = b + if err != nil { + if skipErrors { + fmt.Println("***", err) + } else { + return err + } + } + } + } + return nil +} + +func (cli Zms) importPolicies(dn string, lstPolicies []interface{}, skipErrors bool) error { + for _, policy := range lstPolicies { + policyMap := policy.(map[interface{}]interface{}) + name := policyMap["name"].(string) + fmt.Fprintf(os.Stdout, "Processing policy "+name+"...\n") + assertions := make([]*zms.Assertion, 0) + if val, ok := policyMap["assertions"]; ok { + if val == nil { + fmt.Fprintf(os.Stdout, "Skipping empty policy: "+name+"\n") + continue + } + lst := val.([]interface{}) + if len(lst) > 0 { + for _, a := range lst { + if name == "admin" && a.(string) == "grant * to admin on *" { + continue + } + assertion := strings.Split(a.(string), " ") + newAssertion, err := parseAssertion(dn, assertion) + if err != nil { + if skipErrors { + fmt.Println("***", err) + } else { + return err + } + } + assertions = append(assertions, newAssertion) + } + } + } + if name != "admin" { + _, err := cli.AddPolicyWithAssertions(dn, name, assertions) + if err != nil { + if skipErrors { + fmt.Println("***", err) + } else { + return err + } + } + } + } + return nil +} + +func (cli Zms) generatePublicKeys(lstPublicKeys []interface{}) []*zms.PublicKeyEntry { + publicKeys := make([]*zms.PublicKeyEntry, 0) + if lstPublicKeys != nil { + for _, pubKey := range lstPublicKeys { + publicKeyMap := pubKey.(map[interface{}]interface{}) + // if we're using just version numbers then yaml + // will interpret the key id as integer + var keyId string + switch v := publicKeyMap["keyId"].(type) { + case int: + keyId = strconv.Itoa(v) + case string: + keyId = v + default: + panic("Unknown data type for keyid") + } + value := publicKeyMap["value"].(string) + publicKey := zms.PublicKeyEntry{ + Key: value, + Id: keyId, + } + publicKeys = append(publicKeys, &publicKey) + } + } + return publicKeys +} + +func (cli Zms) importServices(dn string, lstServices []interface{}, skipErrors bool) error { + for _, service := range lstServices { + serviceMap := service.(map[interface{}]interface{}) + name := serviceMap["name"].(string) + fmt.Fprintf(os.Stdout, "Processing service "+name+"...\n") + var lstPublicKeys []interface{} + if val, ok := serviceMap["publicKeys"]; ok { + lstPublicKeys = val.([]interface{}) + } + publicKeys := cli.generatePublicKeys(lstPublicKeys) + _, err := cli.AddServiceWithKeys(dn, name, publicKeys) + if err != nil { + return err + } + if val, ok := serviceMap["providerEndpoint"]; ok { + endpoint := val.(string) + if endpoint != "" { + _, err = cli.SetServiceEndpoint(dn, name, endpoint) + if err != nil { + return err + } + } + } + user := "" + if val, ok := serviceMap["user"]; ok { + user = val.(string) + } + group := "" + if val, ok := serviceMap["group"]; ok { + group = val.(string) + } + exe := "" + if val, ok := serviceMap["executable"]; ok { + exe = val.(string) + } + if user != "" || group != "" || exe != "" { + _, err = cli.SetServiceExe(dn, name, exe, user, group) + if err != nil { + return err + } + } + if val, ok := serviceMap["hosts"]; ok { + hostList := val.([]interface{}) + hosts := make([]string, 0) + for _, host := range hostList { + hosts = append(hosts, host.(string)) + } + _, err = cli.AddServiceHost(dn, name, hosts) + if err != nil { + return err + } + } + } + return nil +} diff --git a/libs/go/zmscli/policy.go b/libs/go/zmscli/policy.go new file mode 100644 index 00000000000..bc58565fd15 --- /dev/null +++ b/libs/go/zmscli/policy.go @@ -0,0 +1,234 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + "fmt" + "strings" + + "github.com/ardielle/ardielle-go/rdl" + "../../../clients/go/zms" +) + +func (cli Zms) policyNames(dn string) ([]string, error) { + names := make([]string, 0) + lst, err := cli.Zms.GetPolicyList(zms.DomainName(dn), nil, "") + if err != nil { + return nil, err + } + for _, n := range lst.Names { + names = append(names, string(n)) + } + return names, nil +} + +func (cli Zms) ListPolicies(dn string) (*string, error) { + var buf bytes.Buffer + policies, err := cli.policyNames(dn) + if err != nil { + return nil, err + } + buf.WriteString("policies:\n") + cli.dumpObjectList(&buf, policies, dn, "policy") + s := buf.String() + return &s, nil +} + +func (cli Zms) ShowPolicy(dn string, name string) (*string, error) { + policy, err := cli.Zms.GetPolicy(zms.DomainName(dn), zms.EntityName(name)) + if err != nil { + return nil, err + } + var buf bytes.Buffer + buf.WriteString("policy:\n") + cli.dumpPolicy(&buf, *policy, indent_level1_dash, indent_level1_dash_lvl) + s := buf.String() + return &s, nil +} + +func parseAssertion(dn string, lst []string) (*zms.Assertion, error) { + err := fmt.Errorf("Bad assertion syntax. Should be ' to on '") + n := len(lst) + var assertion zms.Assertion + if n != 6 { + return nil, err + } + if strings.ToLower(lst[2]) != "to" { + return nil, err + } + if strings.ToLower(lst[4]) != "on" { + return nil, err + } + // we are using grant in our command line utility but the + // actual effect in the spec is ALLOW + effect := strings.ToUpper(lst[0]) + if effect != "GRANT" { + if effect != "DENY" { + return nil, err + } + } else { + effect = "ALLOW" + } + var assertionEffect = zms.NewAssertionEffect(effect) + assertion.Effect = &assertionEffect + assertion.Action = lst[1] + role := lst[3] + if strings.Index(role, ":") < 0 { + role = dn + ":role." + role + } + assertion.Role = role + resource := lst[5] + if strings.Index(resource, ":") < 0 { + resource = dn + ":" + resource + } + assertion.Resource = resource + return &assertion, nil +} + +func (cli Zms) AddPolicyWithAssertions(dn string, pn string, assertions []*zms.Assertion) (*string, error) { + yrn := dn + ":policy." + pn + policy := zms.Policy{ + Name: zms.ResourceName(yrn), + Modified: nil, + Assertions: assertions, + } + err := cli.Zms.PutPolicy(zms.DomainName(dn), zms.EntityName(pn), cli.AuditRef, &policy) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowPolicy(dn, pn) + } +} + +func (cli Zms) AddPolicy(dn string, pn string, assertion []string) (*string, error) { + yrn := dn + ":policy." + pn + _, err := cli.Zms.GetPolicy(zms.DomainName(dn), zms.EntityName(pn)) + if err == nil { + return nil, fmt.Errorf("Policy already exists: %v", yrn) + } else { + switch v := err.(type) { + case rdl.ResourceError: + if v.Code != 404 { + return nil, v + } + } + } + policy := zms.Policy{} + policy.Name = zms.ResourceName(yrn) + if assertion == nil || len(assertion) == 0 { + policy.Assertions = make([]*zms.Assertion, 0) + } else { + newAssertion, err := parseAssertion(dn, assertion) + if err != nil { + return nil, err + } + tmp := [1]*zms.Assertion{newAssertion} + policy.Assertions = tmp[:] + } + err = cli.Zms.PutPolicy(zms.DomainName(dn), zms.EntityName(pn), cli.AuditRef, &policy) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowPolicy(dn, pn) + } +} + +func (cli Zms) AddAssertion(dn string, pn string, assertion []string) (*string, error) { + newAssertion, err := parseAssertion(dn, assertion) + if err != nil { + return nil, err + } + _, err = cli.Zms.PutAssertion(zms.DomainName(dn), zms.EntityName(pn), cli.AuditRef, newAssertion) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowPolicy(dn, pn) + } +} + +func (cli Zms) assertionMatch(assertion1 *zms.Assertion, assertion2 *zms.Assertion) bool { + if assertion1.Action != assertion2.Action { + return false + } + assert1Effect := "ALLOW" + if assertion1.Effect != nil { + assert1Effect = assertion1.Effect.String() + } + assert2Effect := "ALLOW" + if assertion2.Effect != nil { + assert2Effect = assertion2.Effect.String() + } + if assert1Effect != assert2Effect { + return false + } + if assertion1.Resource != assertion2.Resource { + return false + } + if assertion1.Role != assertion2.Role { + return false + } + return true +} + +func (cli Zms) removeAssertion(policy *zms.Policy, deleteAssertion *zms.Assertion) error { + match_index := -1 + for index, assertion := range policy.Assertions { + if cli.assertionMatch(assertion, deleteAssertion) { + match_index = index + break + } + } + if match_index == -1 { + return fmt.Errorf("Policy does not have the specified assertion") + } + policy.Assertions = append(policy.Assertions[:match_index], policy.Assertions[match_index+1:]...) + return nil +} + +func (cli Zms) DeleteAssertion(dn string, pn string, assertion []string) (*string, error) { + policy, err := cli.Zms.GetPolicy(zms.DomainName(dn), zms.EntityName(pn)) + if err != nil { + return nil, err + } + deleteAssertion, err := parseAssertion(dn, assertion) + if err != nil { + return nil, err + } + err = cli.removeAssertion(policy, deleteAssertion) + if err != nil { + return nil, err + } + err = cli.Zms.PutPolicy(zms.DomainName(dn), zms.EntityName(pn), cli.AuditRef, policy) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowPolicy(dn, pn) + } +} + +func (cli Zms) DeletePolicy(dn string, pn string) (*string, error) { + err := cli.Zms.DeletePolicy(zms.DomainName(dn), zms.EntityName(pn), cli.AuditRef) + if err != nil { + return nil, err + } + s := "[Deleted policy: " + pn + "]" + return &s, nil +} diff --git a/libs/go/zmscli/policy_test.go b/libs/go/zmscli/policy_test.go new file mode 100644 index 00000000000..46501742b6c --- /dev/null +++ b/libs/go/zmscli/policy_test.go @@ -0,0 +1,118 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "testing" + + "../../../clients/go/zms" +) + +func TestAssertionMatchTrue(t *testing.T) { + + zms_cli := zms.ZMSClient{ + URL: "dev.zms", + Transport: nil, + CredsHeader: nil, + CredsToken: nil, + Timeout: 0, + } + cli := Zms{"dev.zms", "ntoken", false, false, false, zms_cli, "domain", "", false} + + denyEffect := zms.NewAssertionEffect("DENY") + allowEffect := zms.NewAssertionEffect("ALLOW") + + assertion1 := zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: nil} + assertion2 := zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: nil} + + match := cli.assertionMatch(&assertion1, &assertion2) + if !match { + t.Error("assertion #1 didn't match as expected") + } + + assertion1 = zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: &allowEffect} + assertion2 = zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: nil} + match = cli.assertionMatch(&assertion1, &assertion2) + if !match { + t.Error("assertion #2 didn't match as expected") + } + + assertion1 = zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: nil} + assertion2 = zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: &allowEffect} + match = cli.assertionMatch(&assertion1, &assertion2) + if !match { + t.Error("assertion #3 didn't match as expected") + } + + assertion1 = zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: &allowEffect} + assertion2 = zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: &allowEffect} + match = cli.assertionMatch(&assertion1, &assertion2) + if !match { + t.Error("assertion #4 didn't match as expected") + } + + assertion1 = zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: &denyEffect} + assertion2 = zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: &denyEffect} + match = cli.assertionMatch(&assertion1, &assertion2) + if !match { + t.Error("assertion #5 didn't match as expected") + } +} + +func TestAssertionMatchFalse(t *testing.T) { + + zms_cli := zms.ZMSClient{ + URL: "dev.zms", + Transport: nil, + CredsHeader: nil, + CredsToken: nil, + Timeout: 0, + } + cli := Zms{"dev.zms", "ntoken", false, false, false, zms_cli, "domain", "", false} + + denyEffect := zms.NewAssertionEffect("DENY") + allowEffect := zms.NewAssertionEffect("ALLOW") + + assertion1 := zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: nil} + assertion2 := zms.Assertion{Role: "role2", Resource: "resource", Action: "action", Effect: nil} + match := cli.assertionMatch(&assertion1, &assertion2) + if match { + t.Error("assertion #1 incorrectly matched") + } + + assertion1 = zms.Assertion{Role: "role1", Resource: "resource1", Action: "action", Effect: nil} + assertion2 = zms.Assertion{Role: "role1", Resource: "resource", Action: "action", Effect: nil} + match = cli.assertionMatch(&assertion1, &assertion2) + if match { + t.Error("assertion #2 incorrectly matched") + } + + assertion1 = zms.Assertion{Role: "role1", Resource: "resource1", Action: "action111", Effect: nil} + assertion2 = zms.Assertion{Role: "role1", Resource: "resource1", Action: "action11", Effect: nil} + match = cli.assertionMatch(&assertion1, &assertion2) + if match { + t.Error("assertion #3 incorrectly matched") + } + + assertion1 = zms.Assertion{Role: "role1", Resource: "resource1", Action: "action", Effect: &allowEffect} + assertion2 = zms.Assertion{Role: "role1", Resource: "resource1", Action: "action", Effect: &denyEffect} + match = cli.assertionMatch(&assertion1, &assertion2) + if match { + t.Error("assertion #4 incorrectly matched") + } + + assertion1 = zms.Assertion{Role: "role1", Resource: "resource1", Action: "action", Effect: nil} + assertion2 = zms.Assertion{Role: "role1", Resource: "resource1", Action: "action", Effect: &denyEffect} + match = cli.assertionMatch(&assertion1, &assertion2) + if match { + t.Error("assertion #5 incorrectly matched") + } + + assertion1 = zms.Assertion{Role: "role1", Resource: "resource1", Action: "action", Effect: &denyEffect} + assertion2 = zms.Assertion{Role: "role1", Resource: "resource1", Action: "action", Effect: nil} + match = cli.assertionMatch(&assertion1, &assertion2) + if match { + t.Error("assertion #6 incorrectly matched") + } +} diff --git a/libs/go/zmscli/profile.go b/libs/go/zmscli/profile.go new file mode 100644 index 00000000000..2b865666668 --- /dev/null +++ b/libs/go/zmscli/profile.go @@ -0,0 +1,161 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + "fmt" + "strings" + + "github.com/ardielle/ardielle-go/rdl" + "../../../clients/go/zms" +) + +type Assertion []string + +func (cli Zms) getProfilePolicies(name string) (string, string, map[string]Assertion) { + userProfile := fmt.Sprintf("user.profile.%s", name) + superUserProfile := fmt.Sprintf("superuser.profile.%s", name) + + policies := map[string]Assertion{ + userProfile: []string{"grant", "node_user", "to", userProfile, "on", "node.*"}, + superUserProfile: []string{"grant", "node_sudo", "to", superUserProfile, "on", "node.*"}, + } + return userProfile, superUserProfile, policies +} + +func (cli Zms) addProfileRoles(dn string, names []string) error { + for _, name := range names { + // Add the role, if it doesn't exist + _, err := cli.Zms.GetRole(zms.DomainName(dn), zms.EntityName(name), nil, nil) + if err != nil { + switch v := err.(type) { + case rdl.ResourceError: + if v.Code != 404 { + return v + } + } + + _, err = cli.AddGroupRole(dn, name, []string{}) + if err != nil { + return err + } + } + } + + return nil +} + +func (cli Zms) addProfilePolicies(dn string, policies map[string]Assertion) error { + for name, assertion := range policies { + _, err := cli.Zms.GetPolicy(zms.DomainName(dn), zms.EntityName(name)) + if err != nil { + switch v := err.(type) { + case rdl.ResourceError: + if v.Code != 404 { + return v + } + } + // Policy doesn't exist, add it + _, err = cli.AddPolicy(dn, name, assertion) + if err != nil { + return err + } + } else { + // Policy exists, add the assertion (note: ZMS overrides an existing assertion) + _, err = cli.AddAssertion(dn, name, assertion) + if err != nil { + return err + } + } + } + + return nil +} + +func (cli Zms) isValidProfilePolicy(dn, policyName string, assertion Assertion) (error, bool) { + match := func(zmsAssertion *zms.Assertion) bool { + effect := map[string]zms.AssertionEffect{ + "grant": zms.ALLOW, + "deny": zms.DENY, + } + if *zmsAssertion.Effect == effect[assertion[0]] && + zmsAssertion.Action == assertion[1] && + zmsAssertion.Role == dn+":role."+assertion[3] && + zmsAssertion.Resource == dn+":"+assertion[5] { + return true + } + return false + } + + policy, err := cli.Zms.GetPolicy(zms.DomainName(dn), zms.EntityName(policyName)) + if err != nil { + return err, false + } + + // Policy exists, check for the required assertion + for _, a := range policy.Assertions { + if match(a) { + return nil, true + } + } + return nil, false +} + +func (cli Zms) AddProfile(dn, name string) (*string, error) { + _, err := cli.Zms.GetDomain(zms.DomainName(dn)) + if err != nil { + return nil, err + } + + userProfile, superUserProfile, policies := cli.getProfilePolicies(name) + + err = cli.addProfileRoles(dn, []string{userProfile, superUserProfile}) + if err != nil { + return nil, err + } + + err = cli.addProfilePolicies(dn, policies) + if err != nil { + return nil, err + } + + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowProfile(dn, name) + } +} + +func (cli Zms) ShowProfile(dn, name string) (*string, error) { + userProfile, superUserProfile, policies := cli.getProfilePolicies(name) + + // Verify the expected profile policies and assertions in them + for policyName, assertion := range policies { + err, found := cli.isValidProfilePolicy(dn, policyName, assertion) + if err != nil { + return nil, fmt.Errorf("Profile error: %v", err) + } + if !found { + return nil, fmt.Errorf("Profile error: Assertion (%s) not found in the policy: %s", strings.Join(assertion, " "), policyName) + } + } + + userRole, err := cli.ShowRole(dn, userProfile, false, false) + if err != nil { + return nil, err + } + + superUserRole, err := cli.ShowRole(dn, superUserProfile, false, false) + if err != nil { + return nil, err + } + + content := fmt.Sprintf("%s%s", *userRole, strings.TrimSuffix(*superUserRole, "\n")) + var buf bytes.Buffer + cli.dumpProfile(&buf, name, content) + s := buf.String() + return &s, nil +} diff --git a/libs/go/zmscli/repl.go b/libs/go/zmscli/repl.go new file mode 100644 index 00000000000..f135ceeb0f5 --- /dev/null +++ b/libs/go/zmscli/repl.go @@ -0,0 +1,49 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "github.com/boynton/repl" +) + +func (cli *Zms) Eval(expr string) (string, bool, error) { + if expr == "" { + return "", false, nil + } + params, err := cli.tokenizer(expr) + if err != nil { + return "", false, err + } + result, err := cli.EvalCommand(params) + if result == nil || err != nil { + return "", false, err + } else { + return *result, false, err + } +} + +func (cli *Zms) Prompt() string { + red := "\033[0;31m" + black := "\033[0;0m" + return red + cli.Domain + "> " + black +} + +func (cli *Zms) Reset() { +} + +func (cli *Zms) Complete(expr string) (string, []string) { + return "", nil +} + +func (cli *Zms) Start() []string { + return nil +} + +func (cli *Zms) Stop(history []string) { +} + +func (cli *Zms) Repl() (*string, error) { + repl.REPL(cli) + return nil, nil +} diff --git a/libs/go/zmscli/role.go b/libs/go/zmscli/role.go new file mode 100644 index 00000000000..67ef7d9b230 --- /dev/null +++ b/libs/go/zmscli/role.go @@ -0,0 +1,209 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + "fmt" + "strings" + + "github.com/ardielle/ardielle-go/rdl" + "../../../clients/go/zms" +) + +func providerRoleName(provider, group, action string) string { + // generate provider role group + return provider + ".res_group." + group + "." + action +} + +func (cli Zms) roleNames(dn string) ([]string, error) { + roles := make([]string, 0) + lst, err := cli.Zms.GetRoleList(zms.DomainName(dn), nil, "") + if err != nil { + return nil, err + } + for _, n := range lst.Names { + roles = append(roles, string(n)) + } + return roles, nil +} + +func (cli Zms) ListRoles(dn string) (*string, error) { + var buf bytes.Buffer + roles, err := cli.roleNames(dn) + if err != nil { + return nil, err + } + buf.WriteString("roles:\n") + cli.dumpObjectList(&buf, roles, dn, "role") + s := buf.String() + return &s, nil +} + +func (cli Zms) ShowRole(dn string, rn string, auditLog, expand bool) (*string, error) { + var log *bool + if auditLog { + log = &auditLog + } else { + log = nil + } + var expnd *bool + if expand { + expnd = &expand + } else { + expnd = nil + } + role, err := cli.Zms.GetRole(zms.DomainName(dn), zms.EntityName(rn), log, expnd) + if err != nil { + return nil, err + } + var buf bytes.Buffer + buf.WriteString("role:\n") + cli.dumpRole(&buf, *role, auditLog, indent_level1_dash, indent_level1_dash_lvl) + s := buf.String() + return &s, nil +} + +func (cli Zms) AddDelegatedRole(dn string, rn string, trusted string) (*string, error) { + yrn := dn + ":role." + rn + _, err := cli.Zms.GetRole(zms.DomainName(dn), zms.EntityName(rn), nil, nil) + if err == nil { + return nil, fmt.Errorf("Role already exists: %v", yrn) + } else { + switch v := err.(type) { + case rdl.ResourceError: + if v.Code != 404 { + return nil, v + } + } + } + if rn == "admin" { + return nil, fmt.Errorf("Cannot replace reserved 'admin' role") + } + var role zms.Role + role.Name = zms.ResourceName(yrn) + role.Trust = zms.DomainName(trusted) + err = cli.Zms.PutRole(zms.DomainName(dn), zms.EntityName(rn), cli.AuditRef, &role) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowRole(dn, rn, false, false) + } +} + +func (cli Zms) AddGroupRole(dn string, rn string, members []string) (*string, error) { + yrn := dn + ":role." + rn + var role zms.Role + _, err := cli.Zms.GetRole(zms.DomainName(dn), zms.EntityName(rn), nil, nil) + if err == nil { + return nil, fmt.Errorf("Role already exists: %v", yrn) + } else { + switch v := err.(type) { + case rdl.ResourceError: + if v.Code != 404 { + return nil, v + } + } + } + if rn == "admin" { + return nil, fmt.Errorf("Cannot replace reserved 'admin' role") + } + role.Name = zms.ResourceName(yrn) + m := cli.validatedUsers(members, false) + role.Members = cli.createResourceList(m) + err = cli.Zms.PutRole(zms.DomainName(dn), zms.EntityName(rn), cli.AuditRef, &role) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowRole(dn, rn, false, false) + } +} + +func (cli Zms) DeleteRole(dn string, rn string) (*string, error) { + if rn == "admin" { + return nil, fmt.Errorf("Cannot delete 'admin' role") + } + err := cli.Zms.DeleteRole(zms.DomainName(dn), zms.EntityName(rn), cli.AuditRef) + if err != nil { + return nil, err + } + s := "[Deleted role: " + rn + "]" + return &s, nil +} + +func (cli Zms) AddProviderRoleMembers(dn string, provider string, group string, action string, members []string) (*string, error) { + rn := providerRoleName(provider, group, action) + return cli.AddMembers(dn, rn, members) +} + +func (cli Zms) ShowProviderRoleMembers(dn string, provider string, group string, action string) (*string, error) { + rn := providerRoleName(provider, group, action) + return cli.ShowRole(dn, rn, false, false) +} + +func (cli Zms) DeleteProviderRoleMembers(dn string, provider string, group string, action string, members []string) (*string, error) { + rn := providerRoleName(provider, group, action) + return cli.DeleteMembers(dn, rn, members) +} + +func (cli Zms) AddMembers(dn string, rn string, members []string) (*string, error) { + yrn := dn + ":role." + rn + ms := cli.validatedUsers(members, false) + for _, m := range ms { + var member zms.Membership + member.MemberName = zms.ResourceName(m) + member.RoleName = zms.ResourceName(rn) + err := cli.Zms.PutMembership(zms.DomainName(dn), zms.EntityName(rn), zms.ResourceName(m), cli.AuditRef, &member) + if err != nil { + return nil, err + } + } + var s string + if cli.Verbose { + s = "[Added to " + yrn + ": " + strings.Join(ms, ",") + "]" + } else { + s = "[Added to " + rn + ": " + strings.Join(ms, ",") + "]" + } + return &s, nil +} + +func (cli Zms) DeleteMembers(dn string, rn string, members []string) (*string, error) { + yrn := dn + ":role." + rn + ms := cli.validatedUsers(members, false) + for _, m := range ms { + err := cli.Zms.DeleteMembership(zms.DomainName(dn), zms.EntityName(rn), zms.ResourceName(m), cli.AuditRef) + if err != nil { + return nil, err + } + } + var s string + if cli.Verbose { + s = "[Deleted from " + yrn + ": " + strings.Join(ms, ",") + "]" + } else { + s = "[Deleted from " + rn + ": " + strings.Join(ms, ",") + "]" + } + return &s, nil +} + +func (cli Zms) CheckMembers(dn string, rn string, members []string) (*string, error) { + var buf bytes.Buffer + ms := cli.validatedUsers(members, false) + for _, m := range ms { + member, err := cli.Zms.GetMembership(zms.DomainName(dn), zms.EntityName(rn), zms.ResourceName(m)) + cli.dumpRoleMembership(&buf, *member) + if err != nil { + return nil, err + } + } + s := buf.String() + return &s, nil +} diff --git a/libs/go/zmscli/role_test.go b/libs/go/zmscli/role_test.go new file mode 100644 index 00000000000..6cbc88ada4c --- /dev/null +++ b/libs/go/zmscli/role_test.go @@ -0,0 +1,28 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "testing" +) + +func TestProviderRoleName(t *testing.T) { + + // standard provider/resource group test cases + + rn := providerRoleName("coretech.storage", "articles", "read") + if rn != "coretech.storage.res_group.articles.read" { + t.Error("rolename coretech.storage/articles/read failed") + } + + rn = providerRoleName("coretech.athenz.storage", "articles.docs", "read") + if rn != "coretech.athenz.storage.res_group.articles.docs.read" { + t.Error("rolename coretech.athenz.storage/articles.docs/read failed") + } + + rn = providerRoleName("coretech-athenz.storage", "articles", "read") + if rn != "coretech-athenz.storage.res_group.articles.read" { + t.Error("rolename coretech-athenz.storage/articles/read failed") + } +} diff --git a/libs/go/zmscli/service.go b/libs/go/zmscli/service.go new file mode 100644 index 00000000000..305a416f2d7 --- /dev/null +++ b/libs/go/zmscli/service.go @@ -0,0 +1,362 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + "fmt" + "strings" + + "github.com/ardielle/ardielle-go/rdl" + "../../../clients/go/zms" +) + +func shortname(dn string, sn string) string { + shortName := sn + if strings.HasPrefix(shortName, dn+".") { + shortName = shortName[len(dn)+1:] + } + return shortName +} + +func (cli Zms) serviceNames(dn string) ([]string, error) { + services := make([]string, 0) + lst, err := cli.Zms.GetServiceIdentityList(zms.DomainName(dn), nil, "") + if err != nil { + return nil, err + } + for _, name := range lst.Names { + services = append(services, string(name)) + } + return services, nil +} + +func (cli Zms) ListServices(dn string) (*string, error) { + var buf bytes.Buffer + services, err := cli.serviceNames(dn) + if err != nil { + return nil, err + } + if len(services) == 0 { + buf.WriteString("services: []\n") + } else { + buf.WriteString("services:\n") + cli.dumpObjectList(&buf, services, dn, "service") + } + s := buf.String() + return &s, nil +} + +func (cli Zms) ShowService(dn string, sn string) (*string, error) { + service, err := cli.Zms.GetServiceIdentity(zms.DomainName(dn), zms.SimpleName(sn)) + if err != nil { + return nil, err + } + var buf bytes.Buffer + buf.WriteString("service:\n") + cli.dumpService(&buf, *service, indent_level1_dash, indent_level1_dash_lvl) + s := buf.String() + return &s, nil +} + +func (cli Zms) AddService(dn string, sn string, keyId string, pubKey *string) (*string, error) { + shortName := shortname(dn, sn) + service, err := cli.Zms.GetServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName)) + if err == nil { + return nil, fmt.Errorf("Service identity already exists: " + string(service.Name) + " - use add-public-key to add a key") + } + longName := dn + "." + shortName + publicKeys := make([]*zms.PublicKeyEntry, 0) + publicKey := zms.PublicKeyEntry{ + Key: *pubKey, + Id: keyId, + } + publicKeys = append(publicKeys, &publicKey) + detail := zms.ServiceIdentity{ + Name: zms.ServiceName(longName), + PublicKeys: publicKeys, + ProviderEndpoint: "", + Modified: nil, + Executable: "", + Hosts: nil, + User: "", + Group: "", + } + err = cli.Zms.PutServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName), cli.AuditRef, &detail) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowService(dn, shortName) + } +} + +func (cli Zms) AddProviderService(dn string, sn string, keyId string, pubKey *string) (*string, error) { + shortName := shortname(dn, sn) + service, err := cli.Zms.GetServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName)) + if err == nil { + return nil, fmt.Errorf("Service identity already exists: " + string(service.Name) + " - use add-public-key to add a key") + } + longName := dn + "." + shortName + publicKeys := make([]*zms.PublicKeyEntry, 0) + publicKey := zms.PublicKeyEntry{ + Key: *pubKey, + Id: keyId, + } + publicKeys = append(publicKeys, &publicKey) + detail := zms.ServiceIdentity{ + Name: zms.ServiceName(longName), + PublicKeys: publicKeys, + ProviderEndpoint: "", + Modified: nil, + Executable: "", + Hosts: nil, + User: "", + Group: "", + } + err = cli.Zms.PutServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName), cli.AuditRef, &detail) + if err != nil { + return nil, err + } + // after our service has been created we are going to + // create a self_serve role for this provider + rn := shortName + "_self_serve" + yrn := dn + ":role." + rn + var role zms.Role + _, err = cli.Zms.GetRole(zms.DomainName(dn), zms.EntityName(rn), nil, nil) + if err == nil { + return nil, fmt.Errorf("Provider Service created but Self Serve Role already exists: %v", yrn) + } else { + switch v := err.(type) { + case rdl.ResourceError: + if v.Code != 404 { + return nil, v + } + } + } + role.Name = zms.ResourceName(yrn) + role.Members = make([]zms.ResourceName, 0) + role.Members = append(role.Members, zms.ResourceName(longName)) + err = cli.Zms.PutRole(zms.DomainName(dn), zms.EntityName(rn), cli.AuditRef, &role) + if err != nil { + return nil, err + } + // now that the self_serve role has been created we are + // going to create the self_serve policy for this + // provider that would give access to all tenants + pn := shortName + "_self_serve" + yrn = dn + ":policy." + pn + _, err = cli.Zms.GetPolicy(zms.DomainName(dn), zms.EntityName(pn)) + if err == nil { + return nil, fmt.Errorf("Provider Service created but Self Serve Policy already exists: %v", yrn) + } else { + switch v := err.(type) { + case rdl.ResourceError: + if v.Code != 404 { + return nil, v + } + } + } + policy := zms.Policy{} + policy.Name = zms.ResourceName(yrn) + assertion := make([]string, 0) + assertion = append(assertion, "grant") + assertion = append(assertion, "update") + assertion = append(assertion, "to") + assertion = append(assertion, rn) + assertion = append(assertion, "on") + assertion = append(assertion, "tenant.*") + newAssertion, err := parseAssertion(dn, assertion) + if err != nil { + return nil, err + } + tmp := [1]*zms.Assertion{newAssertion} + policy.Assertions = tmp[:] + err = cli.Zms.PutPolicy(zms.DomainName(dn), zms.EntityName(pn), cli.AuditRef, &policy) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowService(dn, shortName) + } +} + +func (cli Zms) AddServiceWithKeys(dn string, sn string, publicKeys []*zms.PublicKeyEntry) (*string, error) { + shortName := shortname(dn, sn) + service, err := cli.Zms.GetServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName)) + if err == nil { + return nil, fmt.Errorf("Service identity already exists: " + string(service.Name) + " - use add-public-key to add a key") + } + longName := dn + "." + shortName + detail := zms.ServiceIdentity{ + Name: zms.ServiceName(longName), + PublicKeys: publicKeys, + ProviderEndpoint: "", + Modified: nil, + Executable: "", + Hosts: nil, + User: "", + Group: "", + } + err = cli.Zms.PutServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName), cli.AuditRef, &detail) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowService(dn, shortName) + } +} + +func (cli Zms) SetServiceEndpoint(dn string, sn string, endpoint string) (*string, error) { + shortName := shortname(dn, sn) + service, err := cli.Zms.GetServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName)) + if err != nil { + return nil, err + } + service.ProviderEndpoint = endpoint + err = cli.Zms.PutServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName), cli.AuditRef, service) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowService(dn, shortName) + } +} + +func (cli Zms) SetServiceExe(dn string, sn string, exe string, user string, group string) (*string, error) { + shortName := shortname(dn, sn) + service, err := cli.Zms.GetServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName)) + if err != nil { + return nil, err + } + service.Executable = exe + service.User = user + service.Group = group + err = cli.Zms.PutServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName), cli.AuditRef, service) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowService(dn, shortName) + } +} + +func (cli Zms) AddServiceHost(dn string, sn string, hosts []string) (*string, error) { + shortName := shortname(dn, sn) + service, err := cli.Zms.GetServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName)) + if err != nil { + return nil, err + } + if service.Hosts == nil { + service.Hosts = hosts + } else { + for _, host := range hosts { + if !cli.contains(service.Hosts, host) { + service.Hosts = append(service.Hosts, host) + } + } + } + err = cli.Zms.PutServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName), cli.AuditRef, service) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowService(dn, shortName) + } +} + +func (cli Zms) DeleteServiceHost(dn string, sn string, hosts []string) (*string, error) { + shortName := shortname(dn, sn) + service, err := cli.Zms.GetServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName)) + if err != nil { + return nil, err + } + if service.Hosts != nil { + service.Hosts = cli.RemoveAll(service.Hosts, hosts) + err = cli.Zms.PutServiceIdentity(zms.DomainName(dn), zms.SimpleName(shortName), cli.AuditRef, service) + if err != nil { + return nil, err + } + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowService(dn, shortName) + } +} + +func (cli Zms) AddServicePublicKey(dn string, sn string, keyId string, pubKey *string) (*string, error) { + shortName := shortname(dn, sn) + publicKey := zms.PublicKeyEntry{ + Key: *pubKey, + Id: keyId, + } + err := cli.Zms.PutPublicKeyEntry(zms.DomainName(dn), zms.SimpleName(shortName), keyId, cli.AuditRef, &publicKey) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowService(dn, shortName) + } +} + +func (cli Zms) ShowServicePublicKey(dn string, sn string, keyId string) (*string, error) { + var buf bytes.Buffer + shortName := shortname(dn, sn) + pkey, err := cli.Zms.GetPublicKeyEntry(zms.DomainName(dn), zms.SimpleName(shortName), keyId) + if err != nil { + return nil, err + } + buf.WriteString("public-key:\n") + buf.WriteString(indent_level1 + "keyId: " + pkey.Id + "\n") + buf.WriteString(indent_level1 + "value: " + pkey.Key + "\n") + s := buf.String() + return &s, nil +} + +func (cli Zms) DeleteServicePublicKey(dn string, sn string, keyId string) (*string, error) { + shortName := shortname(dn, sn) + err := cli.Zms.DeletePublicKeyEntry(zms.DomainName(dn), zms.SimpleName(shortName), keyId, cli.AuditRef) + if err != nil { + return nil, err + } + if cli.Bulkmode { + s := "" + return &s, nil + } else { + return cli.ShowService(dn, shortName) + } +} + +func (cli Zms) DeleteService(dn string, sn string) (*string, error) { + err := cli.Zms.DeleteServiceIdentity(zms.DomainName(dn), zms.SimpleName(sn), cli.AuditRef) + if err != nil { + return nil, err + } else { + s := "[Deleted service identity: " + dn + "." + sn + "]" + return &s, nil + } +} diff --git a/libs/go/zmscli/service_test.go b/libs/go/zmscli/service_test.go new file mode 100644 index 00000000000..e4efbf90d11 --- /dev/null +++ b/libs/go/zmscli/service_test.go @@ -0,0 +1,23 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "testing" +) + +func TestShortName(t *testing.T) { + sn := shortname("coretech", "service1") + if sn != "service1" { + t.Error("shortname service1 failed") + } + sn = shortname("coretech", "coretech2.service2") + if sn != "coretech2.service2" { + t.Error("shortname service2 failed") + } + sn = shortname("coretech", "coretech.service3") + if sn != "service3" { + t.Error("shortname service3 failed") + } +} diff --git a/libs/go/zmscli/template.go b/libs/go/zmscli/template.go new file mode 100644 index 00000000000..e459f1776e7 --- /dev/null +++ b/libs/go/zmscli/template.go @@ -0,0 +1,82 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + + "../../../clients/go/zms" +) + +func (cli Zms) ListServerTemplates() (*string, error) { + var buf bytes.Buffer + templates, err := cli.Zms.GetServerTemplateList() + if err != nil { + return nil, err + } + buf.WriteString("templates:\n") + for _, name := range templates.TemplateNames { + buf.WriteString(indent_level1_dash + string(name) + "\n") + } + s := buf.String() + return &s, nil +} + +func (cli Zms) ListDomainTemplates(dn string) (*string, error) { + var buf bytes.Buffer + templates, err := cli.Zms.GetDomainTemplateList(zms.DomainName(dn)) + if err != nil { + return nil, err + } + buf.WriteString("templates:\n") + for _, name := range templates.TemplateNames { + buf.WriteString(indent_level1_dash + string(name) + "\n") + } + s := buf.String() + return &s, nil +} + +func (cli Zms) ShowServerTemplate(templateName string) (*string, error) { + template, err := cli.Zms.GetTemplate(zms.SimpleName(templateName)) + if err != nil { + return nil, err + } + var buf bytes.Buffer + buf.WriteString("template:\n") + buf.WriteString(indent_level1 + "roles:\n") + for _, role := range template.Roles { + cli.dumpRole(&buf, *role, false, indent_level2_dash, indent_level2_dash_lvl) + } + buf.WriteString(indent_level1 + "policies:\n") + for _, policy := range template.Policies { + cli.dumpPolicy(&buf, *policy, indent_level2_dash, indent_level2_dash_lvl) + } + + s := buf.String() + return &s, nil +} + +func (cli Zms) SetDomainTemplate(dn string, templates []string) (*string, error) { + templateNames := make([]zms.SimpleName, 0) + for _, value := range templates { + templateNames = append(templateNames, zms.SimpleName(value)) + } + var domainTemplateList zms.DomainTemplate + domainTemplateList.TemplateNames = templateNames + err := cli.Zms.PutDomainTemplate(zms.DomainName(dn), cli.AuditRef, &domainTemplateList) + if err != nil { + return nil, err + } + s := "[Template(s) successfully applied to domain]" + return &s, nil +} + +func (cli Zms) DeleteDomainTemplate(dn string, template string) (*string, error) { + err := cli.Zms.DeleteDomainTemplate(zms.DomainName(dn), zms.SimpleName(template), cli.AuditRef) + if err != nil { + return nil, err + } + s := "[Deleted template: " + template + "]" + return &s, nil +} diff --git a/libs/go/zmscli/tenant.go b/libs/go/zmscli/tenant.go new file mode 100644 index 00000000000..22515181553 --- /dev/null +++ b/libs/go/zmscli/tenant.go @@ -0,0 +1,206 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bytes" + "strings" + + "../../../clients/go/zms" +) + +func (cli Zms) DeleteTenancy(dn string, provider string) (*string, error) { + err := cli.Zms.DeleteTenancy(zms.DomainName(dn), zms.ServiceName(provider), cli.AuditRef) + if err != nil { + return nil, err + } + s := "[Successfully deleted tenant " + dn + " from provider " + provider + "]\n" + return &s, nil +} + +func (cli Zms) AddTenancy(dn string, provider string) (*string, error) { + tenancy := zms.Tenancy{ + Domain: zms.DomainName(dn), + Service: zms.ServiceName(provider), + ResourceGroups: nil, + } + err := cli.Zms.PutTenancy(zms.DomainName(dn), zms.ServiceName(provider), cli.AuditRef, &tenancy) + if err != nil { + return nil, err + } + return cli.ShowTenancy(dn, provider) +} + +func (cli Zms) DeleteTenancyResourceGroup(dn string, provider string, resourceGroup string) (*string, error) { + err := cli.Zms.DeleteTenancyResourceGroup(zms.DomainName(dn), zms.ServiceName(provider), zms.EntityName(resourceGroup), cli.AuditRef) + if err != nil { + return nil, err + } + s := "[Successfully deleted tenant " + dn + " resource group " + resourceGroup + " from provider " + provider + "]\n" + return &s, nil +} + +func (cli Zms) AddTenancyResourceGroup(dn string, provider string, resourceGroup string) (*string, error) { + tenancy := zms.TenancyResourceGroup{ + Domain: zms.DomainName(dn), + Service: zms.ServiceName(provider), + ResourceGroup: zms.EntityName(resourceGroup), + } + err := cli.Zms.PutTenancyResourceGroup(zms.DomainName(dn), zms.ServiceName(provider), zms.EntityName(resourceGroup), cli.AuditRef, &tenancy) + if err != nil { + return nil, err + } + return cli.ShowTenancy(dn, provider) +} + +func (cli Zms) ShowTenancy(dn string, provider string) (*string, error) { + tenancy, err := cli.Zms.GetTenancy(zms.DomainName(dn), zms.ServiceName(provider)) + if err != nil { + return nil, err + } + var buf bytes.Buffer + cli.dumpTenancy(&buf, tenancy, indent_level1) + s := buf.String() + return &s, nil +} + +func (cli Zms) ShowTenantRoles(provDomain string, provService string, tenantDomain string) (*string, error) { + tenantRoles, err := cli.Zms.GetTenantRoles(zms.DomainName(provDomain), zms.SimpleName(provService), zms.DomainName(tenantDomain)) + if err != nil { + return nil, err + } + var buf bytes.Buffer + buf.WriteString("tenant-roles:\n") + cli.dumpTenantRoles(&buf, tenantRoles, indent_level1_dash, indent_level1_dash_lvl) + s := buf.String() + return &s, nil +} + +func (cli Zms) DeleteTenantRoles(provDomain string, provService string, tenantDomain string) (*string, error) { + err := cli.Zms.DeleteTenantRoles(zms.DomainName(provDomain), zms.SimpleName(provService), zms.DomainName(tenantDomain), cli.AuditRef) + if err != nil { + return nil, err + } + s := "[Successfully deleted roles for tenant: " + tenantDomain + "]\n" + return &s, nil +} + +func (cli Zms) AddTenantRoles(provDomain string, provService string, tenantDomain string, roleActions []string) (*string, error) { + tenantRoleActions := make([]*zms.TenantRoleAction, 0) + for _, item := range roleActions { + tokens := strings.Split(item, "=") + if len(tokens) == 2 { + roleToken := zms.TenantRoleAction{ + Role: zms.SimpleName(tokens[0]), + Action: tokens[1], + } + tenantRoleActions = append(tenantRoleActions, &roleToken) + } + } + tenantRoles := zms.TenantRoles{ + Domain: zms.DomainName(provDomain), + Service: zms.SimpleName(provService), + Tenant: zms.DomainName(tenantDomain), + Roles: tenantRoleActions, + } + _, err := cli.Zms.PutTenantRoles(zms.DomainName(provDomain), zms.SimpleName(provService), zms.DomainName(tenantDomain), cli.AuditRef, &tenantRoles) + if err != nil { + return nil, err + } + return cli.ShowTenantRoles(provDomain, provService, tenantDomain) +} + +func (cli Zms) ShowTenantResourceGroupRoles(provDomain string, provService string, tenantDomain string, resourceGroup string) (*string, error) { + tenantRoles, err := cli.Zms.GetTenantResourceGroupRoles(zms.DomainName(provDomain), zms.SimpleName(provService), zms.DomainName(tenantDomain), zms.EntityName(resourceGroup)) + if err != nil { + return nil, err + } + var buf bytes.Buffer + buf.WriteString("resource-group:\n") + cli.dumpTenantResourceGroupRoles(&buf, tenantRoles, indent_level1_dash, indent_level1_dash_lvl) + s := buf.String() + return &s, nil +} + +func (cli Zms) DeleteTenantResourceGroupRoles(provDomain string, provService string, tenantDomain string, resourceGroup string) (*string, error) { + err := cli.Zms.DeleteTenantResourceGroupRoles(zms.DomainName(provDomain), zms.SimpleName(provService), zms.DomainName(tenantDomain), zms.EntityName(resourceGroup), cli.AuditRef) + if err != nil { + return nil, err + } + s := "[Successfully deleted resource group " + resourceGroup + " roles for tenant: " + tenantDomain + "]\n" + return &s, nil +} + +func (cli Zms) AddTenantResourceGroupRoles(provDomain string, provService string, tenantDomain string, resourceGroup string, roleActions []string) (*string, error) { + tenantRoleActions := make([]*zms.TenantRoleAction, 0) + for _, item := range roleActions { + tokens := strings.Split(item, "=") + if len(tokens) == 2 { + roleToken := zms.TenantRoleAction{ + Role: zms.SimpleName(tokens[0]), + Action: tokens[1], + } + tenantRoleActions = append(tenantRoleActions, &roleToken) + } + } + tenantRoles := zms.TenantResourceGroupRoles{ + Domain: zms.DomainName(provDomain), + Service: zms.SimpleName(provService), + Tenant: zms.DomainName(tenantDomain), + Roles: tenantRoleActions, + ResourceGroup: zms.EntityName(resourceGroup), + } + _, err := cli.Zms.PutTenantResourceGroupRoles(zms.DomainName(provDomain), zms.SimpleName(provService), zms.DomainName(tenantDomain), zms.EntityName(resourceGroup), cli.AuditRef, &tenantRoles) + if err != nil { + return nil, err + } + return cli.ShowTenantResourceGroupRoles(provDomain, provService, tenantDomain, resourceGroup) +} + +func (cli Zms) ShowProviderResourceGroupRoles(tenantDomain string, providerDomain string, providerService string, resourceGroup string) (*string, error) { + providerRoles, err := cli.Zms.GetProviderResourceGroupRoles(zms.DomainName(tenantDomain), zms.DomainName(providerDomain), zms.SimpleName(providerService), zms.EntityName(resourceGroup)) + if err != nil { + return nil, err + } + var buf bytes.Buffer + buf.WriteString("resource-group:\n") + cli.dumpProviderResourceGroupRoles(&buf, providerRoles, indent_level1_dash, indent_level1_dash_lvl) + s := buf.String() + return &s, nil +} + +func (cli Zms) DeleteProviderResourceGroupRoles(tenantDomain string, providerDomain string, providerService string, resourceGroup string) (*string, error) { + err := cli.Zms.DeleteProviderResourceGroupRoles(zms.DomainName(tenantDomain), zms.DomainName(providerDomain), zms.SimpleName(providerService), zms.EntityName(resourceGroup), cli.AuditRef) + if err != nil { + return nil, err + } + s := "[Successfully deleted resource group " + resourceGroup + " roles for tenant: " + tenantDomain + "]\n" + return &s, nil +} + +func (cli Zms) AddProviderResourceGroupRoles(tenantDomain string, providerDomain string, providerService string, resourceGroup string, roleActions []string) (*string, error) { + tenantRoleActions := make([]*zms.TenantRoleAction, 0) + for _, item := range roleActions { + tokens := strings.Split(item, "=") + if len(tokens) == 2 { + roleToken := zms.TenantRoleAction{ + Role: zms.SimpleName(tokens[0]), + Action: tokens[1], + } + tenantRoleActions = append(tenantRoleActions, &roleToken) + } + } + providerRoles := zms.ProviderResourceGroupRoles{ + Domain: zms.DomainName(providerDomain), + Service: zms.SimpleName(providerService), + Tenant: zms.DomainName(tenantDomain), + Roles: tenantRoleActions, + ResourceGroup: zms.EntityName(resourceGroup), + } + _, err := cli.Zms.PutProviderResourceGroupRoles(zms.DomainName(tenantDomain), zms.DomainName(providerDomain), zms.SimpleName(providerService), zms.EntityName(resourceGroup), cli.AuditRef, &providerRoles) + if err != nil { + return nil, err + } + return cli.ShowProviderResourceGroupRoles(tenantDomain, providerDomain, providerService, resourceGroup) +} diff --git a/libs/go/zmscli/utils.go b/libs/go/zmscli/utils.go new file mode 100644 index 00000000000..51310b75e99 --- /dev/null +++ b/libs/go/zmscli/utils.go @@ -0,0 +1,111 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "bufio" + "strings" + + "../../../clients/go/zms" +) + +func split(data []byte, atEOF bool) (advance int, token []byte, err error) { + + // create a split function using the existing word scanner + // we're going to look for the quoted strings and handle them accordingly + + advance, token, err = bufio.ScanWords(data, atEOF) + if err == nil && token != nil { + if token[0] == '"' { + var advance_fwd int + var token_fwd []byte + for { + advance_fwd, token_fwd, err = bufio.ScanWords(data[advance:], atEOF) + if err != nil || token_fwd == nil { + return + } + advance += advance_fwd + token = append(token, 32) + token = append(token, token_fwd...) + if token_fwd[len(token_fwd)-1] == '"' { + token = token[1 : len(token)-1] + break + } + } + } + } + return +} + +func (cli Zms) createResourceList(items []string) []zms.ResourceName { + list := make([]zms.ResourceName, 0) + for _, item := range items { + list = append(list, zms.ResourceName(item)) + } + return list +} + +func (cli Zms) createStringList(items []zms.ResourceName) []string { + list := make([]string, 0) + for _, item := range items { + list = append(list, string(item)) + } + return list +} + +func (cli Zms) tokenizer(input string) ([]string, error) { + + scanner := bufio.NewScanner(strings.NewReader(input)) + scanner.Split(split) + + tokens := make([]string, 0) + for scanner.Scan() { + tokens = append(tokens, scanner.Text()) + } + err := scanner.Err() + return tokens, err +} + +func indexOfString(s []string, match string) int { + for i, ss := range s { + if ss == match { + return i + } + } + return -1 +} + +func (cli Zms) validatedUsers(users []string, forceSelf bool) []string { + validatedUsers := make([]string, 0) + for _, v := range users { + if strings.Index(v, ".") < 0 { + validatedUsers = append(validatedUsers, cli.UserDomain+"."+v) + } else { + validatedUsers = append(validatedUsers, v) + } + } + if forceSelf && indexOfString(validatedUsers, cli.Identity) < 0 { + validatedUsers = append(validatedUsers, cli.Identity) + } + return validatedUsers +} + +func (cli Zms) contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +func (cli Zms) RemoveAll(fullList []string, removeList []string) []string { + var newList []string + for _, item := range fullList { + if !cli.contains(removeList, item) { + newList = append(newList, item) + } + } + return newList +} diff --git a/libs/go/zmscli/utils_test.go b/libs/go/zmscli/utils_test.go new file mode 100644 index 00000000000..70edfba92b8 --- /dev/null +++ b/libs/go/zmscli/utils_test.go @@ -0,0 +1,170 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package zmscli + +import ( + "testing" +) + +func TestTokenizer(t *testing.T) { + cli := Zms{} + var list []string + var err error + list, err = cli.tokenizer("one two three") + if err != nil { + t.Error("Tokenizer returned error") + } + if len(list) != 3 { + t.Error("Number of tokens is not 3") + } + if list[0] != "one" { + t.Error("First element is not one") + } + if list[1] != "two" { + t.Error("Second element is not two") + } + if list[2] != "three" { + t.Error("Third element is not three") + } + list, err = cli.tokenizer("set-meta-details \"Description is long\" two three") + if len(list) != 4 { + t.Error("Number of quoted string tokens is not 4") + } + if list[0] != "set-meta-details" { + t.Error("First element is not set-meta-details") + } + if list[1] != "Description is long" { + t.Error("Second element is not 'Description is long' : " + list[1]) + } + if list[2] != "two" { + t.Error("Third element is not two") + } + if list[3] != "three" { + t.Error("Fourth element is not three") + } +} + +func TestIndexOfString(t *testing.T) { + list := []string{"one", "two", "three", "four"} + if indexOfString(list, "one") != 0 { + t.Error("indexOfStrings case 'one' failed") + } + if indexOfString(list, "three") != 2 { + t.Error("indexOfStrings case 'three' failed") + } + if indexOfString(list, "four") != 3 { + t.Error("indexOfStrings case 'four' failed") + } + if indexOfString(list, "five") != -1 { + t.Error("indexOfStrings case 'five' failed") + } +} + +func TestValidatedUsers(t *testing.T) { + cli := Zms{} + cli.UserDomain = "user" + cli.identity = "user.user1" + members := []string{"user.user2", "user3"} + list := cli.validatedUsers(members, false) + if len(list) != 2 { + t.Error("Returned list is not length 2") + } + if list[0] != "user.user2" { + t.Error("First member is not user.user2") + } + if list[1] != "user.user3" { + t.Error("Second member is not user.user3") + } + list = cli.validatedUsers(members, true) + if len(list) != 3 { + t.Error("Returned list is not length 3") + } + if list[0] != "user.user2" { + t.Error("First member is not user.user2") + } + if list[1] != "user.user3" { + t.Error("Second member is not user.user3") + } + if list[2] != "user.user1" { + t.Error("Third member is not user.user1") + } + members = []string{"user.user2", "user1"} + list = cli.validatedUsers(members, false) + if len(list) != 2 { + t.Error("Returned list is not length 2") + } + if list[0] != "user.user2" { + t.Error("First member is not user.user2") + } + if list[1] != "user.user1" { + t.Error("Second member is not user.user1") + } +} + +func TestContains(t *testing.T) { + cli := Zms{} + list := []string{"one", "two", "three", "four"} + if !cli.contains(list, "one") { + t.Error("contains case 'one' failed") + } + if !cli.contains(list, "three") { + t.Error("contains case 'three' failed") + } + if !cli.contains(list, "four") { + t.Error("contains case 'four' failed") + } + if cli.contains(list, "five") { + t.Error("contains case 'five' failed") + } +} + +func TestRemoveAll(t *testing.T) { + cli := Zms{} + fullList := []string{"one", "two", "three"} + removeList := []string{"two", "four"} + list := cli.RemoveAll(fullList, removeList) + if len(list) != 2 { + t.Error("Returned list is not length 2") + } + if list[0] != "one" { + t.Error("First member is not one") + } + if list[1] != "three" { + t.Error("Second member is not three") + } + fullList = []string{"two", "four"} + list = cli.RemoveAll(fullList, removeList) + if len(list) != 0 { + t.Error("Returned list is not empty") + } +} + +func TestGetHttpTransport(t *testing.T) { + tr := getHttpTransport(nil, false) + if tr != nil { + t.Error("Did not receive nil transport") + } + tr = getHttpTransport(nil, true) + if tr.TLSClientConfig == nil { + t.Error("Only TLSConfig not set correctly") + } + if tr.Dial != nil { + t.Error("Only TLSConfig - Dial is not nil") + } + proxy := "socks.athenzcompany.com:10080" + tr = getHttpTransport(&proxy, false) + if tr.TLSClientConfig != nil { + t.Error("Only Proxy - TLSConfig not set correctly") + } + if tr.Dial == nil { + t.Error("Only Proxy - Dial is nil") + } + tr = getHttpTransport(&proxy, true) + if tr.TLSClientConfig == nil { + t.Error("Both optionss - TLSConfig not set correctly") + } + if tr.Dial == nil { + t.Error("Both options - Dial is not set correctly") + } +} diff --git a/libs/java/auth_core/README.md b/libs/java/auth_core/README.md new file mode 100644 index 00000000000..31a84db84c4 --- /dev/null +++ b/libs/java/auth_core/README.md @@ -0,0 +1,10 @@ +auth-core +========= + +Core interfaces for authorization + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/libs/java/auth_core/pom.xml b/libs/java/auth_core/pom.xml new file mode 100644 index 00000000000..7a56d1b1254 --- /dev/null +++ b/libs/java/auth_core/pom.xml @@ -0,0 +1,98 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../../pom.xml + + + auth_core + jar + auth_core + Core Auth Interfaces + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + + + org.kohsuke + libpam4j + 1.6 + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + prepare-package + + jar + + + + + + + + + + platform-mac + + false + + mac + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + kerberos-tests + + + + default-test + + test + + + + + + + + + + diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Authority.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Authority.java new file mode 100644 index 00000000000..cba446c59bb --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Authority.java @@ -0,0 +1,89 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth; + +import java.security.cert.X509Certificate; + +/** + * An Authority can validate credentials of a Principal in its domain. It also can provide HTTP header information + * that determines where to find relevant credentials for that task. + */ +public interface Authority { + + /** + * Source for the credentials - either headers or certificate + */ + enum CredSource { + HEADER, + CERTIFICATE + } + + /** + * Initialize the authority + */ + public void initialize(); + + /** + * @return credentials source - headers or certificate with headers being default + */ + default public CredSource getCredSource() { + return CredSource.HEADER; + } + + /** + * @return the domain of the authority, i.e. "user" or "local", as defined by the authorization system + */ + public String getDomain(); + + /** + * @return a string describing where to find the credentials in a request, i.e. "X-Auth-Token" or "Cookie.User" + */ + public String getHeader(); + + /** + * @return a boolean flag indicating whether or not authenticated principals + * by this authority are allowed to be "authorized" to make changes. If this + * flag is true, then the principal must first get a ZMS UserToken and then + * use that UserToken for subsequent operations. + */ + + default public boolean allowAuthorization() { + return true; + } + + /** + * Verify the credentials and if valid return the corresponding Principal, null otherwise. + * @param creds the credentials (i.e. cookie, token, secret) that will identify the principal. + * @param remoteAddr remote IP address of the connection + * @param httpMethod the http method for this request (e.g. GET, PUT, etc) + * @param errMsg will contain error message if authenticate fails + * @return the Principal for the credentials, or null if the credentials are not valid. + */ + public Principal authenticate(String creds, String remoteAddr, String httpMethod, StringBuilder errMsg); + + /** + * Process the client certificates extracted from the http request object. + * Extract the CN field from the Certificate Subject DN which should be the Athenz + * Service Identity and return a corresponding Principal object. In case any exceptions, + * a null object is returned + * @param certs an array of X509 certificates retrieved from the request + * @param errMsg will contain error message if authenticate fails + * @return the Principal for the certificate, or null in case of failure. + */ + default public Principal authenticate(X509Certificate[] certs, StringBuilder errMsg) { + return null; + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/AuthorityKeyStore.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/AuthorityKeyStore.java new file mode 100644 index 00000000000..c7c6594cf7c --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/AuthorityKeyStore.java @@ -0,0 +1,21 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth; + +public interface AuthorityKeyStore { + + void setKeyStore(KeyStore keyStore); +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Authorizer.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Authorizer.java new file mode 100644 index 00000000000..ce8263dbca3 --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Authorizer.java @@ -0,0 +1,34 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth; + +/** + * An Authorizer is an entity that can authorize an assertion of access. An assertion consists of + * an action performed on a resource by a principal. The resource includes its own domain information + *, but an extra crossDomain argument can be specified to check another domain's idea of access on the + * resource (to handle a cross-domain trust scenario). Normally the crossDomain argument should be null + */ +public interface Authorizer { + /** + * Check access, return true if access is granted, false otherwise. + * @param resource - (YRN) the resource to check access against. Must include the domain. + * @param action - (CompoundName) the action to check access for + * @param principal - (ResourceName) the principal who will access the resource. + * @param crossDomain - (DomainName) an alternate domain responsible for the policy involved. This is usually null. + * @return true if access is granted for the action/resource/principal + */ + public boolean access(String action, String resource, Principal principal, String crossDomain); +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/KeyStore.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/KeyStore.java new file mode 100644 index 00000000000..d78391f126d --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/KeyStore.java @@ -0,0 +1,29 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth; + +public interface KeyStore { + + /** + * Return the PEM encoded public key for the given key id and service. + * The key which should be ybase64decoded prior to return if was ybase64encoded. + * @param domain Name of the domain + * @param service Name of the service + * @param keyId the public key identifier + * @return String with PEM encoded key + */ + String getPublicKey(String domain, String service, String keyId); +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Principal.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Principal.java new file mode 100644 index 00000000000..d259b71d84b --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/Principal.java @@ -0,0 +1,67 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth; + +import java.util.List; + +/** + * A Principal is an authenticated entity that takes an action on a resource. + */ +public interface Principal { + + /** @return the domain of the authority over this principal, i.e. "user" */ + public String getDomain(); + + /** @return the name of the principal as a string, i.e. "joe" */ + public String getName(); + + /** @return the YRN of the principal as a string, i.e. "user.joe" */ + public String getYRN(); + + /** @return the credentials token as a string */ + public String getCredentials(); + + /** @return the credentials token as a string but will not contain a signature */ + public String getUnsignedCredentials(); + + /** @return the list of roles this principal is able to assume. This is null + * for user/service principals, but valid for a principal based on ZTokens. */ + public List getRoles(); + + /** @return the authority over this principal. Can be null, if not authenticated. */ + public Authority getAuthority(); + + /** @return the issue time for the credentials */ + public long getIssueTime(); + + /** @return the service name that was authorized to use the Principal's UserToken */ + public String getAuthorizedService(); + + /** @return the associated IP address provided in the principal token */ + default String getIP() { + return null; + } + + /** @return the associated original requestor specified in the principal token */ + default String getOriginalRequestor() { + return null; + } + + /** @return the associated original key service specified in the principal token */ + default String getKeyService() { + return null; + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/ServiceIdentityProvider.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/ServiceIdentityProvider.java new file mode 100644 index 00000000000..34e699d210a --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/ServiceIdentityProvider.java @@ -0,0 +1,31 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth; + +/** + * The interface for a service identity provider. The container of the service defines the actual + * implementation. + */ +public interface ServiceIdentityProvider { + + /** + * @param domainName the name of the domain + * @param serviceName the name of the service + * @return the identity of the service in the form of a Principal. + */ + public Principal getIdentity(String domainName, String serviceName); + +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/CertificateAuthority.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/CertificateAuthority.java new file mode 100644 index 00000000000..d27cd064d90 --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/CertificateAuthority.java @@ -0,0 +1,134 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import java.security.cert.X509Certificate; + +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; + +public class CertificateAuthority implements Authority { + + private static final Logger LOG = LoggerFactory.getLogger(CertificateAuthority.class); + + @Override + public void initialize() { + } + + @Override + public String getDomain() { + return null; + } + + @Override + public String getHeader() { + return null; + } + + @Override + public Principal authenticate(String creds, String remoteAddr, String httpMethod, StringBuilder errMsg) { + return null; + } + + @Override + public CredSource getCredSource() { + return CredSource.CERTIFICATE; + } + + String extractX509CertCN(X509Certificate x509Cert) { + + String cn = null; + try { + String principalName = x509Cert.getSubjectX500Principal().getName(); + if (LOG.isDebugEnabled()) { + LOG.debug("CertificateAuthority:authenticate: TLS Certificate Principal: " + principalName); + } + if (principalName != null && !principalName.isEmpty()) { + + // in case there are multiple CNs, we're only looking at the first one + + X500Name x500name = new X500Name(x509Cert.getSubjectX500Principal().getName()); + RDN cnRdn = x500name.getRDNs(BCStyle.CN)[0]; + cn = IETFUtils.valueToString(cnRdn.getFirst().getValue()); + } + } catch (Exception ex) { + if (LOG.isDebugEnabled()) { + LOG.debug("CertificateAuthority:authenticate: Unable to extract principal", ex); + } + } + return cn; + } + + @Override + public Principal authenticate(X509Certificate[] certs, StringBuilder errMsg) { + + if (LOG.isDebugEnabled()) { + LOG.debug("CertificateAuthority:authenticate: TLS Certificates: " + certs); + if (certs != null) { + for (X509Certificate cert : certs) { + LOG.debug("CertificateAuthority:authenticate: TLS Certificate: " + cert); + } + } + } + + errMsg = errMsg == null ? new StringBuilder(512) : errMsg; + + // make sure we have at least one valid certificate in our list + + if (certs == null || certs[0] == null) { + errMsg.append("CertificateAuthority:authenticate: No certificate available in request"); + return null; + } + + X509Certificate x509Cert = certs[0]; + String principalName = extractX509CertCN(x509Cert); + if (principalName == null || principalName.isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug("CertificateAuthority:authenticate: Certificate principal is empty"); + } + errMsg.append("CertificateAuthority:authenticate: Certificate principal is empty"); + return null; + } + + // extract domain and service names from the name. We must have + // a valid service identity in the form domain.service + + int idx = principalName.lastIndexOf('.'); + if (idx == -1) { + errMsg.append("CertificateAuthority:authenticate: Principal is not a valid service identity: " + + principalName); + return null; + } + + String domain = principalName.substring(0, idx); + String name = principalName.substring(idx + 1); + + // all the role members in Athenz are normalized to lower case so we need to make + // sure our principal's name and domain are created with lower case as well + + SimplePrincipal princ = (SimplePrincipal) SimplePrincipal.create(domain.toLowerCase(), + name.toLowerCase(), x509Cert.toString(), this); + princ.setUnsignedCreds(x509Cert.getSubjectX500Principal().toString()); + return princ; + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/KerberosAuthority.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/KerberosAuthority.java new file mode 100644 index 00000000000..74a90a158f6 --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/KerberosAuthority.java @@ -0,0 +1,352 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import java.util.Set; +import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicReference; + +import javax.security.auth.Subject; +import javax.security.auth.kerberos.KerberosPrincipal; +import javax.security.auth.kerberos.KerberosTicket; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginContext; +import javax.security.auth.callback.CallbackHandler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.token.KerberosToken; + +/** + * An Authority can validate credentials of a Principal in its domain. It also can provide HTTP header information + * that determines where to find relevant credentials for that task. + */ +public class KerberosAuthority implements Authority { + + static final Logger LOG = LoggerFactory.getLogger(KerberosAuthority.class); + + static final String KRB_AUTH_HEADER = "Authorization"; + static final String KRB_PROP_SVCPRPL = "athenz.auth.kerberos.service_principal"; + static final String KRB_PROP_KEYTAB = "athenz.auth.kerberos.keytab_location"; + static final String KRB_PROP_DEBUG = "athenz.auth.kerberos.debug"; + + // This is used if there is a jaas.conf. The jaas.conf path is specified by the system property + // java.security.auth.login.config + static final String KRB_PROP_JAASCFG = "athenz.auth.kerberos.jaas_cfg_section"; + static final String KRB_PROP_LOGIN_CB_CLASS = "athenz.auth.kerberos.login_callback_handler_class"; + static final String KRB_PROP_LOGIN_RENEW_TGT = "athenz.auth.kerberos.renewTGT"; // "true" or "false" + static final String KRB_PROP_LOGIN_USE_TKT_CACHE = "athenz.auth.kerberos.use_ticket_cache"; // "true" or "false" + static final String KRB_PROP_LOGIN_TKT_CACHE_NAME = "athenz.auth.kerberos.ticket_cache_name"; // file path + static final String KRB_PROP_LOGIN_WINDOW = "athenz.auth.kerberos.login_window"; // millis used to determine re-login + + static final String LOGIN_WINDOW_DEF = "60000"; // 60 seconds default + + private String servicePrincipal; // ex: HTTP/localhost@LOCALHOST + private String keyTabConfFile; + private String jaasConfigSection; + private String loginCallbackHandler; + private AtomicReference serviceSubject = new AtomicReference(); + private Exception initState = null; + + private long lastLogin = 0; // last time logged in in millisecs + private long loginWindow = 60000; + + public KerberosAuthority(String servicePrincipal, String keyTabConfFile, String jaasConfigSection) { + + this(); + if (servicePrincipal != null) { + this.servicePrincipal = servicePrincipal; + } + if (keyTabConfFile != null) { + this.keyTabConfFile = keyTabConfFile; + } + if (jaasConfigSection == null) { + this.jaasConfigSection = ""; + } else { + this.jaasConfigSection = jaasConfigSection; + } + } + + public KerberosAuthority() { + servicePrincipal = System.getProperty(KRB_PROP_SVCPRPL); + keyTabConfFile = System.getProperty(KRB_PROP_KEYTAB); + jaasConfigSection = System.getProperty(KRB_PROP_JAASCFG, ""); + loginCallbackHandler = System.getProperty(KRB_PROP_LOGIN_CB_CLASS); + loginWindow = Long.decode(System.getProperty(KRB_PROP_LOGIN_WINDOW, LOGIN_WINDOW_DEF)); + } + + public Exception getInitState() { + return initState; + } + public void setInitState(Exception exc) { + initState = exc; + } + + public long getLoginWindow() { + return loginWindow; + } + public void setLoginWindow(long loginWindowMillis) { + loginWindow = loginWindowMillis; + } + + public long getLastLogin() { + return lastLogin; + } + + /** + * Initialize the authority + */ + @Override + public void initialize() { + login(false); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public synchronized void login(boolean logoutFirst) { + + long now = System.currentTimeMillis(); + if ((now - lastLogin) < loginWindow) { + // recently logged in so dont bother do it again + return; + } + + Subject subject = null; + if (servicePrincipal != null) { + Set principals = new HashSet(1); + principals.add(new KerberosPrincipal(servicePrincipal)); + + subject = new Subject(false, principals, new HashSet(), new HashSet()); + } + + LoginConfig loginConfig = new LoginConfig(keyTabConfFile, servicePrincipal); + initState = null; + try { + // NOTE: if no callback handler specified + // LoginContext uses the auth.login.defaultCallbackHandler security property for the fully + // qualified class name of a default handler implementation + LoginContext loginContext = null; + CallbackHandler loginHandler = null; + if (loginCallbackHandler != null) { + Class cbhandlerClass = Class.forName(loginCallbackHandler); + loginHandler = (CallbackHandler) cbhandlerClass.getConstructor(String.class, String.class).newInstance(servicePrincipal, null); + } + + if (subject == null) { + loginContext = new LoginContext(jaasConfigSection, loginHandler); + } else { + loginContext = new LoginContext(jaasConfigSection, subject, loginHandler, loginConfig); + } + + if (logoutFirst) { + loginContext.logout(); + } + loginContext.login(); + subject = loginContext.getSubject(); + serviceSubject.set(subject); + lastLogin = System.currentTimeMillis(); + + } catch (Exception exc) { + initState = exc; + String params = "svc-princ=" + servicePrincipal + " login-callback=" + loginCallbackHandler + + " keytab=" + keyTabConfFile + " jaas-section=" + jaasConfigSection; + LOG.error("KerberosAuthority:initialize: Login context failure: config params=(" + params + ") exc: " + exc.getMessage(), exc); + } + } + + boolean isTargetPrincipal(KerberosTicket ticket, String remoteSvcPrincipal) { + if (ticket == null) { + return false; + } + + KerberosPrincipal principal = ticket.getServer(); + if (LOG.isDebugEnabled()) { + LOG.debug("KerberosAuthority:isTargetPrincipal: our princ=" + servicePrincipal + " ticket princ=" + principal.getName()); + } + + if (principal.getName().equals(remoteSvcPrincipal)) { + return true; + } + return false; + } + + /** + * Determines if refresh login needed if the ticket for the specified + * remoteSvcPrincipal has expired or is not found. + * @param remoteSvcPrincipal remote service principal + * @return true if refresh is required, false otherwise (ticket is still valid) + */ + public boolean refreshLogin(String remoteSvcPrincipal) { + // check for expiration + // get the original ticket from the serviceSubject + Subject subject = serviceSubject.get(); + KerberosTicket tgt = null; + Set tickets = subject.getPrivateCredentials(KerberosTicket.class); + for (KerberosTicket ticket : tickets) { + if (isTargetPrincipal(ticket, remoteSvcPrincipal)) { + tgt = ticket; + break; + } + } + + if (tgt == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("KerberosAuthority:refreshLogin: Process tickets found no principal match: subject contains number of tickets=" + tickets.size()); + } + return true; + } + long end = tgt.getEndTime().getTime(); + long now = System.currentTimeMillis(); + if (now > end) { + login(true); + return true; + } + return false; + } + + /** + * @return the domain of the authority, defaults to "ygrid" + */ + @Override + public String getDomain() { + return null; + } + + @Override + public String getHeader() { + return KRB_AUTH_HEADER; + } + + /** + * Verify the credentials and if valid return the corresponding Principal, null otherwise. + * @param creds the credentials (i.e. cookie, token, secret) that will identify the principal. + * @param remoteAddr remote IP address of the connection + * @param httpMethod the http method for this request (e.g. GET, PUT, etc) + * @param errMsg will contain error message if authenticate fails + * @return the Principal for the credentials, or null if the credentials are not valid. + */ + @Override + public Principal authenticate(String creds, String remoteAddr, String httpMethod, StringBuilder errMsg) { + + KerberosToken token = null; + try { + token = new KerberosToken(creds, remoteAddr); + } catch (IllegalArgumentException ex) { + if (errMsg == null) { + errMsg = new StringBuilder(); + } + errMsg.append("KerberosAuthority:authenticate: Invalid token: exc="). + append(ex.getMessage()).append(" : credential="). + append(creds); + LOG.error("KerberosAuthority:authenticate: " + errMsg.toString()); + return null; + } + + StringBuilder errDetail = new StringBuilder(512); + if (token.validate(serviceSubject.get(), errDetail) == false) { + if (errMsg != null) { + errMsg.append("KerberosAuthority:authenticate: token validation failure: "); + errMsg.append(errDetail); + } + return null; + } + + String userDomain = token.getDomain(); + String userName = token.getUserName(); + if (userName == null) { + if (errMsg != null) { + errMsg.append("KerberosAuthority:authenticate: token validation failure: missing user"); + } + return null; + } + return SimplePrincipal.create(userDomain, userName, creds, this); + } + + static class LoginConfig extends Configuration { + private String keyTabConfFile; + private String servicePrincipalName; + private boolean debugKrbEnabled = false; + + public LoginConfig(String keyTabConfFile, String servicePrincipalName) { + this.keyTabConfFile = keyTabConfFile; + this.servicePrincipalName = servicePrincipalName; + debugKrbEnabled = Boolean.parseBoolean(System.getProperty(KRB_PROP_DEBUG, "false")); + } + + public boolean isDebugEnabled() { + return debugKrbEnabled; + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + Map options = new HashMap(); + if (keyTabConfFile == null || keyTabConfFile.isEmpty()) { + options.put("useKeyTab", "false"); + options.put("tryFirstPass", "true"); + } else { + options.put("useKeyTab", "true"); + options.put("keyTab", keyTabConfFile); + if (LOG.isDebugEnabled()) { + LOG.debug("KerberosAuthority:authenticate: use keytab=" + keyTabConfFile); + } + } + options.put("principal", servicePrincipalName); + options.put("storeKey", "true"); + options.put("doNotPrompt", "true"); + String useTktCache = System.getProperty(KRB_PROP_LOGIN_USE_TKT_CACHE, "true"); + options.put("useTicketCache", useTktCache); + String renewTgt = System.getProperty(KRB_PROP_LOGIN_RENEW_TGT, "true"); + options.put("renewTGT", renewTgt); + /* + * refreshKrb5Config is very important. Without that not able to login + * more than one principal. Fails with + * "KeyTab instance already exists. Unable to obtain password from user" + */ + options.put("refreshKrb5Config", "true"); + + // If "ticketCache" is set, "useTicketCache" must also be set to true; + // Otherwise a configuration error will be returned + if (Boolean.parseBoolean(useTktCache) == true) { + String ticketCacheName = System.getenv("KRB5CCNAME"); // this is what hadoop does + if (ticketCacheName != null) { + options.put("ticketCache", ticketCacheName); + } else { + ticketCacheName = System.getProperty(KRB_PROP_LOGIN_TKT_CACHE_NAME); + if (ticketCacheName != null) { + options.put("ticketCache", ticketCacheName); + } + } + } + + if (debugKrbEnabled) { + options.put("debug", "true"); + } + options.put("isInitiator", "false"); + + return new AppConfigurationEntry[] { + new AppConfigurationEntry( + "com.sun.security.auth.module.Krb5LoginModule", + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options) + }; + } + } +} + diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/PrincipalAuthority.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/PrincipalAuthority.java new file mode 100644 index 00000000000..bae9cabb61b --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/PrincipalAuthority.java @@ -0,0 +1,319 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.AuthorityKeyStore; +import com.yahoo.athenz.auth.KeyStore; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.token.PrincipalToken; +import com.yahoo.athenz.auth.token.Token; + +public class PrincipalAuthority implements Authority, AuthorityKeyStore { + + private static final String USER_DOMAIN = "user"; + private static final String SYS_AUTH_DOMAIN = "sys.auth"; + private static final String ZMS_SERVICE = "zms"; + private static final String ZTS_SERVICE = "zts"; + + static final String ATHENZ_PROP_TOKEN_OFFSET = "athenz.auth.principal.token_allowed_offset"; + static final String ATHENZ_PROP_IP_CHECK_MODE = "athenz.auth.principal.remote_ip_check_mode"; + static final String ATHENZ_PROP_USER_DOMAIN = "athenz.user_domain"; + + public static final String HTTP_HEADER = "Athenz-Principal-Auth"; + public static final String ATHENZ_PROP_PRINCIPAL_HEADER = "athenz.auth.principal.header"; + + private static final Logger LOG = LoggerFactory.getLogger(PrincipalAuthority.class); + + enum IpCheckMode { + OPS_ALL, + OPS_WRITE, + OPS_NONE + } + + KeyStore keyStore = null; + private int allowedOffset = 300; + IpCheckMode ipCheckMode = IpCheckMode.OPS_WRITE; + String userDomain = USER_DOMAIN; + final String headerName = System.getProperty(ATHENZ_PROP_PRINCIPAL_HEADER, HTTP_HEADER); + + @Override + public void initialize() { + + allowedOffset = Integer.parseInt(System.getProperty(ATHENZ_PROP_TOKEN_OFFSET, "300")); + ipCheckMode = IpCheckMode.valueOf(System.getProperty(ATHENZ_PROP_IP_CHECK_MODE, IpCheckMode.OPS_WRITE.toString())); + userDomain = System.getProperty(ATHENZ_PROP_USER_DOMAIN, USER_DOMAIN); + + // case of invalid value, we'll default back to 5 minutes + + if (allowedOffset < 0) { + allowedOffset = 300; + } + } + + @Override + public String getDomain() { + return null; //services *are* a domain + } + + @Override + public String getHeader() { + return headerName; + } + + @Override + public Principal authenticate(String signedToken, String remoteAddr, String httpMethod, StringBuilder errMsg) { + + errMsg = errMsg == null ? new StringBuilder(512) : errMsg; + if (LOG.isDebugEnabled()) { + LOG.debug("Authenticating PrincipalToken: " + signedToken); + } + + PrincipalToken serviceToken = null; + try { + serviceToken = new PrincipalToken(signedToken); + } catch (IllegalArgumentException ex) { + errMsg.append("PrincipalAuthority:authenticate: Invalid token: exc="). + append(ex.getMessage()).append(" : credential="). + append(Token.getUnsignedToken(signedToken)); + LOG.error(errMsg.toString()); + return null; + } + + /* before authenticating verify that if this is a valid + * authorized service token or not and if required + * components are provided (the method already logs + * all error messages) */ + + StringBuilder errDetail = new StringBuilder(512); + if (!serviceToken.isValidAuthorizedServiceToken(errDetail)) { + errMsg.append("PrincipalAuthority:authenticate: Invalid authorized service token: "); + errMsg.append(errDetail).append(" : credential="). + append(Token.getUnsignedToken(signedToken)); + return null; + } + + String tokenDomain = serviceToken.getDomain().toLowerCase(); + String tokenName = serviceToken.getName().toLowerCase(); + String keyService = serviceToken.getKeyService(); + boolean userToken = tokenDomain.equals(userDomain); + + /* get the public key for this token to validate signature */ + + String publicKey = getPublicKey(tokenDomain, tokenName, keyService, + serviceToken.getKeyId(), userToken); + + /* the validate method logs all error messages */ + + if (serviceToken.validate(publicKey, allowedOffset, errDetail) == false) { + errMsg.append("PrincipalAuthority:authenticate: service token validation failure: "); + errMsg.append(errDetail).append(" : credential="). + append(Token.getUnsignedToken(signedToken)); + return null; + } + + /* if an authorized service signature is available then we're going to validate + * that signature as well to support token chaining in Athenz and, if necessary, + * bypass IP address mismatch for users */ + + String authorizedServiceName = null; + if (serviceToken.getAuthorizedServiceSignature() != null) { + authorizedServiceName = validateAuthorizeService(serviceToken, errDetail); + if (authorizedServiceName == null) { + errMsg.append("PrincipalAuthority:authenticate: validation of authorized service failure: "). + append(errDetail).append(" : credential="). + append(Token.getUnsignedToken(signedToken)); + return null; + } + } + + /* if we have a usertoken and our remote ip check enabled, verify that the IP address + * matches before allowing the operation go through */ + + if (userToken && !remoteIpCheck(remoteAddr, httpMethod, serviceToken, authorizedServiceName)) { + errMsg.append("PrincipalAuthority:authenticate: IP Mismatch - token ("). + append(serviceToken.getIP()).append(") request ("). + append(remoteAddr).append(")"); + LOG.error(errMsg.toString()); + return null; + } + + /* all the role members in Athenz are normalized to lower case so we need to make + * sure our principal's name and domain are created with lower case as well */ + + SimplePrincipal princ = (SimplePrincipal) SimplePrincipal.create(tokenDomain, + tokenName, signedToken, serviceToken.getTimestamp(), this); + princ.setUnsignedCreds(serviceToken.getUnsignedToken()); + princ.setAuthorizedService(authorizedServiceName); + princ.setOriginalRequestor(serviceToken.getOriginalRequestor()); + princ.setKeyService(keyService); + princ.setIP(serviceToken.getIP()); + return princ; + } + + boolean remoteIpCheck(String remoteAddr, String httpMethod, PrincipalToken serviceToken, + String authorizedServiceName) { + + boolean checkResult = true; + switch (ipCheckMode) { + case OPS_ALL: + if (!remoteAddr.equals(serviceToken.getIP())) { + checkResult = false; + } + break; + case OPS_WRITE: + /* if we have a user token for a write operation and we have an IP address + * mismatch then we'll allow this authenticate request to proceed only if it's + * been configured with authorized user only. */ + + if (isWriteOperation(httpMethod) && !remoteAddr.equals(serviceToken.getIP())) { + + if (authorizedServiceName == null) { + checkResult = false; + } + } + break; + default: + break; + } + + return checkResult; + } + + String getPublicKey(String tokenDomain, String tokenName, String keyService, + String keyId, boolean userToken) { + + /* by default we're going to look for the public key for the domain + * and service defined in the token */ + + String publicKeyDomain = tokenDomain; + String publicKeyService = tokenName; + + /* now let's handle the exceptions: + * 1) if the token has a key service field set then only supported values are + * either zms or zts, so we use sys.auth.zms or sys.auth.zts services + * 2) if the token's domain is user then it's a user token or if it's sd then + * it's our special project token so for those cases we are going to ask for + * zms's own public key. */ + + if (keyService != null && !keyService.isEmpty()) { + if (keyService.equals(ZMS_SERVICE)) { + publicKeyDomain = SYS_AUTH_DOMAIN; + publicKeyService = ZMS_SERVICE; + } else if (keyService.equals(ZTS_SERVICE)) { + publicKeyDomain = SYS_AUTH_DOMAIN; + publicKeyService = ZTS_SERVICE; + } + } else if (userToken) { + publicKeyDomain = SYS_AUTH_DOMAIN; + publicKeyService = ZMS_SERVICE; + } + + return keyStore.getPublicKey(publicKeyDomain, publicKeyService, keyId); + } + + boolean isWriteOperation(String httpMethod) { + if (httpMethod == null) { + return false; + } + if (httpMethod.equalsIgnoreCase("PUT") || httpMethod.equalsIgnoreCase("POST") + || httpMethod.equalsIgnoreCase("DELETE")) { + return true; + } else { + return false; + } + } + + String getAuthorizedServiceName(List authorizedServices, String authorizedServiceName) { + + /* if we have an authorized service name specified then it must be + * present in the authorized services list or if it's null then the + * list must contain a single element only */ + + String serviceName = authorizedServiceName; + if (serviceName == null) { + if (authorizedServices.size() != 1) { + LOG.error("getAuthorizedServiceName() failed: No authorized service name specified"); + return null; + } + serviceName = authorizedServices.get(0); + } else { + if (!authorizedServices.contains(serviceName)) { + LOG.error("getAuthorizedServiceName() failed: Invalid authorized service name specified:" + + serviceName); + return null; + } + } + return serviceName; + } + + String validateAuthorizeService(PrincipalToken userToken, StringBuilder errMsg) { + + errMsg = errMsg == null ? new StringBuilder(512) : errMsg; + + /* if we have an authorized service name specified then it must be + * present in the authorized services list or if it's null then the + * list must contain a single element only */ + + String authorizedServiceName = userToken.getAuthorizedServiceName(); + if (authorizedServiceName == null) { + List authorizedServices = userToken.getAuthorizedServices(); + if (authorizedServices == null || authorizedServices.size() != 1) { + errMsg.append("PrincipalAuthority:validateAuthorizeService: "). + append("No service name and services list empty OR contains multiple entries: token="). + append(userToken.getUnsignedToken()); + return null; + } else { + authorizedServiceName = authorizedServices.get(0); + } + } + + /* need to extract domain and service name from our full service name value */ + + int idx = authorizedServiceName.lastIndexOf('.'); + if (idx <= 0 || idx == authorizedServiceName.length() - 1) { + errMsg.append("PrincipalAuthority:validateAuthorizeService: "). + append("failed: token=").append(userToken.getUnsignedToken()). + append(" : Invalid authorized service name specified="). + append(authorizedServiceName); + LOG.error(errMsg.toString()); + return null; + } + + String publicKey = keyStore.getPublicKey(authorizedServiceName.substring(0, idx), + authorizedServiceName.substring(idx + 1), userToken.getAuthorizedServiceKeyId()); + + /* the token method reports all error messages */ + StringBuilder errDetail = new StringBuilder(512); + if (userToken.validateForAuthorizedService(publicKey, errDetail) == false) { + errMsg.append("PrincipalAuthority:validateAuthorizeService: token validation for authorized service failed: "). + append(errDetail); + return null; + } + + return authorizedServiceName; + } + + @Override + public void setKeyStore(KeyStore keyStore) { + this.keyStore = keyStore; + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/RoleAuthority.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/RoleAuthority.java new file mode 100644 index 00000000000..8585df14b4d --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/RoleAuthority.java @@ -0,0 +1,157 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.AuthorityKeyStore; +import com.yahoo.athenz.auth.KeyStore; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.token.RoleToken; +import com.yahoo.athenz.auth.token.Token; + +public class RoleAuthority implements Authority, AuthorityKeyStore { + + private static final Logger LOG = LoggerFactory.getLogger(RoleAuthority.class); + + public final static String SYS_AUTH_DOMAIN = "sys.auth"; + public final static String ZTS_SERVICE_NAME = "zts"; + private static final String USER_DOMAIN = "user"; + + static final String ATHENZ_PROP_TOKEN_OFFSET = "athenz.auth.role.token_allowed_offset"; + static final String ATHENZ_PROP_USER_DOMAIN = "athenz.user_domain"; + + public static final String HTTP_HEADER = "Athenz-Role-Auth"; + public static final String ATHENZ_PROP_ROLE_HEADER = "athenz.auth.role.header"; + + private int allowedOffset = 300; + + KeyStore keyStore = null; + String userDomain = "user"; + final String headerName = System.getProperty(ATHENZ_PROP_ROLE_HEADER, HTTP_HEADER); + + @Override + public void initialize() { + + allowedOffset = Integer.parseInt(System.getProperty(ATHENZ_PROP_TOKEN_OFFSET, "300")); + userDomain = System.getProperty(ATHENZ_PROP_USER_DOMAIN, USER_DOMAIN); + + // case of invalid value, we'll default back to 5 minutes + + if (allowedOffset < 0) { + allowedOffset = 300; + } + } + + @Override + public String getDomain() { + return "sys.auth"; + } + + @Override + public String getHeader() { + return headerName; + } + + @Override + public Principal authenticate(String signedToken, String remoteAddr, String httpMethod, StringBuilder errMsg) { + + errMsg = errMsg == null ? new StringBuilder(512) : errMsg; + if (LOG.isDebugEnabled()) { + LOG.debug("Authenticating RoleToken: " + signedToken); + } + + RoleToken roleToken = null; + try { + roleToken = new RoleToken(signedToken); + } catch (IllegalArgumentException ex) { + errMsg.append("RoleAuthority:authenticate failed: Invalid token: exc="). + append(ex.getMessage()).append(" : credential="). + append(Token.getUnsignedToken(signedToken)); + LOG.error(errMsg.toString()); + return null; + } + + /* if the token's domain is user then we need to check to see if this is a write + * operation (PUT/POST/DELETE) and in that case we must validate the IP + * address of the incoming request to make sure it matches to IP address + * that's stored in the RoleToken */ + + if (!remoteAddr.equals(roleToken.getIP()) && isWriteOperation(httpMethod)) { + + String tokenPrincipal = roleToken.getPrincipal(); + int idx = tokenPrincipal.lastIndexOf('.'); + if (idx <= 0 || idx == tokenPrincipal.length() - 1) { + errMsg.append("RoleAuthority:authenticate failed: Invalid principal specified: "). + append(tokenPrincipal).append(" : credential="). + append(Token.getUnsignedToken(signedToken)); + LOG.error(errMsg.toString()); + return null; + } + + if (tokenPrincipal.substring(0, idx).equalsIgnoreCase(userDomain)) { + errMsg.append("RoleAuthority:authenticate failed: IP Mismatch - token-ip("). + append(roleToken.getIP()).append(") request-addr("). + append(remoteAddr).append(") : credential="). + append(Token.getUnsignedToken(signedToken)); + if (LOG.isWarnEnabled()) { + LOG.warn(errMsg.toString()); + } + + return null; + } + } + + String publicKey = keyStore.getPublicKey(SYS_AUTH_DOMAIN, ZTS_SERVICE_NAME, roleToken.getKeyId()); + + if (roleToken.validate(publicKey, allowedOffset) == false) { + errMsg.append("RoleAuthority:authenticate failed: validation was not successful: credential="). + append(Token.getUnsignedToken(signedToken)); + if (LOG.isWarnEnabled()) { + LOG.warn(errMsg.toString()); + } + return null; + } + + // all the role members in Athenz are normalized to lower case so we need to make + // sure our principal's name and domain are created with lower case as well + + SimplePrincipal princ = (SimplePrincipal) SimplePrincipal.create(roleToken.getDomain().toLowerCase(), + signedToken, roleToken.getRoles(), this); + princ.setUnsignedCreds(roleToken.getUnsignedToken()); + return princ; + } + + boolean isWriteOperation(String httpMethod) { + if (httpMethod == null) { + return false; + } + if (httpMethod.equalsIgnoreCase("PUT") || httpMethod.equalsIgnoreCase("POST") + || httpMethod.equalsIgnoreCase("DELETE")) { + return true; + } else { + return false; + } + } + + @Override + public void setKeyStore(KeyStore keyStore) { + this.keyStore = keyStore; + } + +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/SimplePrincipal.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/SimplePrincipal.java new file mode 100644 index 00000000000..29647ee027c --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/SimplePrincipal.java @@ -0,0 +1,233 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.util.Validate; + +public class SimplePrincipal implements Principal { + + private static final Logger LOG = LoggerFactory.getLogger(SimplePrincipal.class); + + String domain = null; + String name = null; + String yrn = null; + String creds = null; + String unsignedCreds = null; + String ip = null; + long issueTime = 0; + List roles = null; + Authority authority = null; + String authorizedService = null; + String originalRequestor = null; + String keyService = null; + + public static Principal create(String domain, String name, String creds) { + return create(domain, name, creds, 0, null); + } + + /** + * Create a Principal based on a given RoleToken + * @param domain Domain name that the RoleToken was issued for + * @param creds Credentials of the principal (RoleToken) + * @param roles List of roles defined in the token + * @param authority authority responsible for the credentials (RoleAuthority) + * @return a Principal for the given set of roles in a domain + */ + public static Principal create(String domain, String creds, List roles, Authority authority) { + if (!Validate.domainName(domain)) { + if (LOG.isWarnEnabled()) { + LOG.warn("WARNING: domain name doesn't validate: " + creds); + } + } + if (roles == null || roles.size() == 0) { + if (LOG.isWarnEnabled()) { + LOG.warn("WARNING: zero roles: " + creds); + } + } + return new SimplePrincipal(domain, creds, roles, authority); + } + + /** + * Create a Principal for the given identity + * @param domain Domain name for the identity + * @param name Name of the identity + * @param creds Credentials of the principal (PrincipalToken which could be either UserToken or ServiceToken) + * @param authority authority responsible for the credentials (e.g. PrincipalAuthority) + * @return a Principal for the identity + */ + public static Principal create(String domain, String name, String creds, Authority authority) { + return create(domain, name, creds, 0, authority); + } + + /** + * Create a Principal for the given user identity + * @param domain Domain name for the identity (For users this will always be user) + * @param name Name of the identity + * @param creds Credentials of the principal (e.g. Cookie.User) + * @param issueTime when the User Cookie/Credentials was issued + * @param authority authority responsible for the credentials (e.g. UserAuthority) + * @return a Principal for the identity + */ + public static Principal create(String domain, String name, String creds, long issueTime, Authority authority) { + + if (!Validate.principalName(name)) { + if (LOG.isWarnEnabled()) { + LOG.warn("WARNING: principal name doesn't validate: " + name); + } + } + + if (domain != null) { + String matchDomain = (authority == null) ? null : authority.getDomain(); + if (matchDomain != null && !domain.equals(matchDomain)) { + if (LOG.isWarnEnabled()) { + LOG.warn("FAIL: domain mismatch for user " + name + " in authority + " + authority); + } + return null; + } + } else if (authority != null) { + if (authority.getDomain() != null) { + if (LOG.isWarnEnabled()) { + LOG.warn("FAIL: domain mismatch for user " + name + " in authority + " + authority); + } + return null; + } + } + return new SimplePrincipal(domain, name, creds, issueTime, authority); + } + + /** + * Create a Principal for the given host identity + * @param appId Application identifer + * @param creds Credentials of the principal + * @param authority authority responsible for the credentials (e.g. HostAuthority) + * @return a Principal for the host identity + */ + public static Principal create(String appId, String creds, Authority authority) { + return new SimplePrincipal(null, appId, creds, 0, authority); + } + + private SimplePrincipal(String domain, String name, String creds, long issueTime, Authority authority) { + this.domain = domain; + this.name = name; + this.creds = creds; + this.authority = authority; + this.issueTime = issueTime; + } + + private SimplePrincipal(String domain, String creds, List roles, Authority authority) { + this.domain = domain; + this.creds = creds; + this.roles = roles; + this.authority = authority; + } + + public void setUnsignedCreds(String unsignedCreds) { + this.unsignedCreds = unsignedCreds; + } + + public void setAuthorizedService(String authorizedService) { + this.authorizedService = authorizedService; + } + + public void setIP(String ip) { + this.ip = ip; + } + + public void setOriginalRequestor(String originalRequestor) { + this.originalRequestor = originalRequestor; + } + + public void setKeyService(String keyService) { + this.keyService = keyService; + } + + public String getIP() { + return ip; + } + + public String getUnsignedCredentials() { + return unsignedCreds; + } + + public Authority getAuthority() { + return authority; + } + + public String getDomain() { + return domain; + } + + public String getName() { + return name; + } + + public String getOriginalRequestor() { + return originalRequestor; + } + + public String getYRN() { + + if (yrn == null) { + if (domain != null && name != null) { + StringBuilder str = new StringBuilder(256); + str.append(domain); + str.append("."); + str.append(name); + yrn = str.toString(); + } else if (domain != null) { + yrn = domain; + } else if (name != null) { + yrn = name; + } + } + + return yrn; + } + public String getCredentials() { + return creds; + } + + public List getRoles() { + return roles; + } + + public long getIssueTime() { + return issueTime; + } + + public String toString() { + if (roles == null) { + return domain + "." + name; + } else { + return "ZToken_" + domain + "~" + roles.toString().replace("[", "").replace("]", ""); + } + } + + public String getAuthorizedService() { + return authorizedService; + } + + public String getKeyService() { + return keyService; + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/SimpleServiceIdentityProvider.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/SimpleServiceIdentityProvider.java new file mode 100644 index 00000000000..5635f6acb98 --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/SimpleServiceIdentityProvider.java @@ -0,0 +1,106 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.ServiceIdentityProvider; +import com.yahoo.athenz.auth.token.PrincipalToken; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.auth.util.CryptoException; + +import java.io.File; +import java.net.InetAddress; +import java.security.PrivateKey; + +/** + * The SimpleServiceIdentityProvider does proper signing of an NToken, but + * doesn't do anything to verify that the caller *should* have the private + * key. For stand-alone services, a stronger guarantee is recommended. This class + * could be used as a base class, to sign the credentials + */ +public class SimpleServiceIdentityProvider implements ServiceIdentityProvider { + + private PrivateKey key = null; + private Authority authority = null; + private long tokenTimeout = 3600; + private String keyId = "0"; + private String host = null; + + public SimpleServiceIdentityProvider(Authority authority, File privateKeyFile) throws CryptoException { + this(authority, Crypto.loadPrivateKey(privateKeyFile), "0", 3600); + } + + public SimpleServiceIdentityProvider(Authority authority, String privateKey) throws CryptoException { + this(authority, Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privateKey)), "0", 3600); + } + + public SimpleServiceIdentityProvider(Authority authority, PrivateKey privateKey) { + this(authority, privateKey, "0", 3600); + } + + public SimpleServiceIdentityProvider(Authority authority, PrivateKey privateKey, long tokenTimeout) { + this(authority, privateKey, "0", tokenTimeout); + } + + public SimpleServiceIdentityProvider(Authority authority, PrivateKey privateKey, + String keyId, long tokenTimeout) { + this.authority = authority; + this.key = privateKey; + this.tokenTimeout = tokenTimeout; + this.keyId = keyId.toLowerCase(); + this.setHost(getServerHostName()); + } + + public Principal getIdentity(String domainName, String serviceName) { + + // all the role members in Athenz are normalized to lower case so we need to make + // sure our principal's name and domain are created with lower case as well + + domainName = domainName.toLowerCase(); + serviceName = serviceName.toLowerCase(); + + PrincipalToken token = new PrincipalToken.Builder("S1", domainName, serviceName) + .expirationWindow(tokenTimeout).host(host).keyId(keyId).build(); + token.sign(key); + + SimplePrincipal princ = (SimplePrincipal) SimplePrincipal.create(domainName, + serviceName, token.getSignedToken(), System.currentTimeMillis() / 1000, authority); + princ.setUnsignedCreds(token.getUnsignedToken()); + return princ; + } + + String getServerHostName() { + + String urlhost = null; + try { + InetAddress localhost = java.net.InetAddress.getLocalHost(); + urlhost = localhost.getCanonicalHostName(); + } catch (java.net.UnknownHostException e) { + urlhost = "localhost"; + } + + return urlhost; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/UserAuthority.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/UserAuthority.java new file mode 100644 index 00000000000..9aded1f0cd6 --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/UserAuthority.java @@ -0,0 +1,142 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; + +import java.nio.charset.StandardCharsets; + +import org.bouncycastle.util.encoders.Base64; +import org.jvnet.libpam.PAM; +import org.jvnet.libpam.PAMException; +import org.jvnet.libpam.UnixUser; + +/** + * Implementation that performs validation of PAM. + */ +public class UserAuthority implements Authority { + + private static final Logger LOG = LoggerFactory.getLogger(UserAuthority.class); + static final String ATHENZ_PROP_PAM_SERVICE_NAME = "athenz.auth.user.pam_service_name"; + + final String serviceName = System.getProperty(ATHENZ_PROP_PAM_SERVICE_NAME, "login"); + private PAM pam = null; + + public UserAuthority() { + } + + @Override + public void initialize() { + } + + @Override + public String getDomain() { + return "user"; + } + + @Override + public String getHeader() { + return "Authorization"; + }; + + /* + * we don't want the user to keep specifying their username and + * password as part of the request. instead, the user must first + * request a usertoken and then use that usertoken for all other + * requests against ZMS and ZTS servers. + * @see com.yahoo.athenz.auth.Authority#allowAuthorization() + */ + @Override + public boolean allowAuthorization() { + return false; + } + + void setPAM(PAM pam) { + this.pam = pam; + } + + PAM getPAM() throws PAMException { + if (pam != null) { + return pam; + } + return new PAM(serviceName); + } + + @Override + public Principal authenticate(String creds, String remoteAddr, String httpMethod, StringBuilder errMsg) { + errMsg = errMsg == null ? new StringBuilder(512) : errMsg; + + // the HTTP Basic authorization format is: Basic base64(:) + + if (!creds.startsWith("Basic ")) { + errMsg.append("UserAuthority:authenticate: credentials do not start with 'Basic '"); + LOG.error(errMsg.toString()); + return null; + } + + // decode - need to skip the first 6 bytes for 'Basic ' + + String decoded; + try { + decoded = new String(Base64.decode(creds.substring(6).getBytes(StandardCharsets.UTF_8))); + } catch (Exception e) { + errMsg.append("UserAuthority:authenticate: factory exc=").append(e.getMessage()); + LOG.error(errMsg.toString()); + return null; + } + + String[] userArray = decoded.split(":"); + String username = userArray[0]; + String password = userArray[1]; + + // we need to catch all exceptions here and just return + // failure to allow other authorities to handle authentication + // if necessary + + UnixUser user = null; + try { + user = getPAM().authenticate(username, password); + } catch (Throwable ex) { + errMsg.append("UserAuthority:authenticate: failed: user=").append(username). + append(" exc=").append(ex.getMessage()); + LOG.error(errMsg.toString()); + return null; + } + + if (user == null) { + errMsg.append("UserAuthority:authenticate: failed: user=").append(username); + LOG.error(errMsg.toString()); + return null; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("UserAuthority.authenticate: valid user=" + username); + } + + // all the role members in Athenz are normalized to lower case so we need to make + // sure our principal's name and domain are created with lower case as well + + long issueTime = 0; + SimplePrincipal princ = (SimplePrincipal) SimplePrincipal.create(getDomain().toLowerCase(), + userArray[0].toLowerCase(), creds, issueTime, this); + princ.setUnsignedCreds(creds); + return princ; + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/KerberosToken.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/KerberosToken.java new file mode 100644 index 00000000000..d0c13fc3a8c --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/KerberosToken.java @@ -0,0 +1,131 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.token; + +import java.security.PrivilegedExceptionAction; +import java.nio.charset.StandardCharsets; +import java.security.PrivilegedActionException; +import javax.security.auth.Subject; + +import org.bouncycastle.util.encoders.Base64; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class KerberosToken extends Token { + + private static final Logger LOG = LoggerFactory.getLogger(KerberosToken.class); + + public static final String KRB_AUTH_VAL_FLD = "Negotiate"; + public static final String KRB_PROP_TOKEN_PRIV_ACTION = "athenz.auth.kerberos.krb_privileged_action_class"; + public static final String ATHENZ_PROP_USER_DOMAIN = "athenz.user_domain"; + public static final String ATHENZ_PROP_USER_REALM = "athenz.auth.kerberos.user_realm"; + public static final String ATHENZ_PROP_KRB_USER_DOMAIN = "athenz.auth.kerberos.krb_user_domain"; + public static final String ATHENZ_PROP_KRB_USER_REALM = "athenz.auth.kerberos.krb_user_realm"; + + String krbPrivActionClass = System.getProperty(KRB_PROP_TOKEN_PRIV_ACTION); + String userName = null; + + public static final String USER_DOMAIN = System.getProperty(ATHENZ_PROP_USER_DOMAIN, "user"); + public static final String USER_REALM = System.getProperty(ATHENZ_PROP_USER_REALM, "USER_REALM"); + public static final String KRB_USER_DOMAIN = System.getProperty(ATHENZ_PROP_KRB_USER_DOMAIN, "krb"); + public static final String KRB_USER_REALM = System.getProperty(ATHENZ_PROP_KRB_USER_REALM, "KRB_REALM"); + + public KerberosToken(String creds, String remoteAddr) { + if (creds == null || creds.isEmpty()) { + LOG.error("KerberosToken: Missing credentials"); + throw new IllegalArgumentException("KerberosToken: creds must not be empty"); + } + + if (!creds.startsWith(KRB_AUTH_VAL_FLD)) { + throw new IllegalArgumentException("KerberosToken: creds do not contain required Negotiate component"); + } + + signedToken = creds; + unsignedToken = creds.substring(KRB_AUTH_VAL_FLD.length()).trim(); + domain = KRB_USER_DOMAIN; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public boolean validate(Subject serviceSubject, StringBuilder errMsg) { + + byte[] kerberosTicket = Base64.decode(unsignedToken.getBytes(StandardCharsets.UTF_8)); + PrivilegedExceptionAction privExcAction = null; + try { + if (krbPrivActionClass == null) { + privExcAction = new KerberosValidateAction(kerberosTicket); + } else { + Class privActionClass = Class.forName(krbPrivActionClass); + privExcAction = (PrivilegedExceptionAction) privActionClass.getConstructor(byte[].class).newInstance(kerberosTicket); + } + userName = Subject.doAs(serviceSubject, privExcAction); + int index = userName.indexOf('@'); + if (index != -1) { + if (userName.indexOf(KRB_USER_REALM, index) == -1) { + if (userName.indexOf(USER_REALM, index) != -1) { + domain = USER_DOMAIN; + } else { + throw new Exception("KerberosToken:validate: invalid Kerberos Realm: " + userName); + } + } + userName = userName.substring(0, index); + } + + return true; + } catch (PrivilegedActionException paexc) { + if (errMsg == null) { + errMsg = new StringBuilder(512); + } + errMsg.append("KerberosToken:validate: token=").append(unsignedToken). + append(" : privilege exc=").append(paexc); + LOG.error(errMsg.toString()); + paexc.printStackTrace(); + } catch (Exception exc) { + if (errMsg == null) { + errMsg = new StringBuilder(512); + } + errMsg.append("KerberosToken:validate: token=").append(unsignedToken). + append(" : unknown exc=").append(exc); + LOG.error(errMsg.toString()); + exc.printStackTrace(); + } + return false; + } + + public String getUserName() { + return userName; + } + + private static class KerberosValidateAction implements PrivilegedExceptionAction { + byte[] kerberosTicket; + + public KerberosValidateAction(byte[] kerberosTicket) { + this.kerberosTicket = kerberosTicket; + } + + @Override + public String run() throws Exception { + GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null); + context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length); + String user = context.getSrcName().toString(); + context.dispose(); + return user; + } + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/PrincipalToken.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/PrincipalToken.java new file mode 100644 index 00000000000..71a3a0eecaf --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/PrincipalToken.java @@ -0,0 +1,498 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.token; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.auth.util.CryptoException; + +public class PrincipalToken extends Token { + + private String name = null; + private String originalRequestor = null; + protected String keyService = null; + private List authorizedServices = null; + private String authorizedServiceName = null; + private String authorizedServiceKeyId = "0"; + private String authorizedServiceSignature = null; + + private static final Logger LOG = LoggerFactory.getLogger(PrincipalToken.class); + + public static class Builder { + + // required attributes + private String domain = null; + private String name = null; + private String version = null; + + // optional attributes with default values + private String salt = Crypto.randomSalt(); + private String host = null; + private String ip = null; + private String keyId = "0"; + private long expirationWindow = 3600; + private long issueTime = 0; + private List authorizedServices = null; + private String keyService = null; + private String originalRequestor = null; + + public Builder(String version, String domain, String name) { + + if (version == null || domain == null || name == null) { + throw new IllegalArgumentException("version, domain and name parameters must not be null."); + } + + if (version.isEmpty() || domain.isEmpty() || name.isEmpty()) { + throw new IllegalArgumentException("version, domain and name parameters must have values."); + } + + this.version = version; + this.domain = domain; + this.name = name; + } + + public Builder host(String value) { + this.host = value; + return this; + } + + public Builder salt(String value) { + this.salt = value; + return this; + } + + public Builder ip(String value) { + this.ip = value; + return this; + } + + public Builder keyId(String value) { + this.keyId = value; + return this; + } + + public Builder issueTime(long value) { + this.issueTime = value; + return this; + } + + public Builder expirationWindow(long value) { + this.expirationWindow = value; + return this; + } + + public Builder authorizedServices(List authorizedServices) { + this.authorizedServices = authorizedServices; + return this; + } + + public Builder keyService(String value) { + this.keyService = value; + return this; + } + + public Builder originalRequestor(String value) { + this.originalRequestor = value; + return this; + } + + public PrincipalToken build() { + return new PrincipalToken(this); + } + } + + private PrincipalToken(Builder builder) { + + this.version = builder.version; + this.domain = builder.domain; + this.name = builder.name; + this.host = builder.host; + this.salt = builder.salt; + this.keyId = builder.keyId; + this.ip = builder.ip; + this.authorizedServices = builder.authorizedServices; + this.keyService = builder.keyService; + this.originalRequestor = builder.originalRequestor; + + super.setTimeStamp(builder.issueTime, builder.expirationWindow); + + StringBuilder strBuilder = new StringBuilder(defaultBuilderBufSize); + + strBuilder.append("v="); + strBuilder.append(version); + strBuilder.append(";d="); + strBuilder.append(domain); + strBuilder.append(";n="); + strBuilder.append(name); + + if (host != null && !host.isEmpty()) { + strBuilder.append(";h="); + strBuilder.append(host); + } + + strBuilder.append(";a="); + strBuilder.append(salt); + strBuilder.append(";t="); + strBuilder.append(timestamp); + strBuilder.append(";e="); + strBuilder.append(expiryTime); + strBuilder.append(";k="); + strBuilder.append(keyId); + if (keyService != null && !keyService.isEmpty()) { + strBuilder.append(";z="); + strBuilder.append(keyService); + } + if (originalRequestor != null && !originalRequestor.isEmpty()) { + strBuilder.append(";o="); + strBuilder.append(originalRequestor); + } + if (ip != null && !ip.isEmpty()) { + strBuilder.append(";i="); + strBuilder.append(ip); + } + if (authorizedServices != null && !authorizedServices.isEmpty()) { + strBuilder.append(";b="); + strBuilder.append(String.join(",", authorizedServices)); + } + + unsignedToken = strBuilder.toString(); + + if (LOG.isDebugEnabled()) { + LOG.debug("PrincipalToken created: " + unsignedToken); + } + } + + public PrincipalToken(String signedToken) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Constructing PrincipalToken with input string: " + signedToken); + } + + if (signedToken == null || signedToken.isEmpty()) { + throw new IllegalArgumentException("Input String signedToken must not be empty"); + } + + /** + * first we need to extract data and signature parts + * the signature is always at the end of the token. The principal + * token can represent 2 types - service or user. The version + * string identifies the type by using S or U. Here are two sample + * tokens: + * + * User: + * v=U1;d=user;n=john;a=salt;t=tstamp;e=expiry;s=sig + * + * Service: + * v=S1;d=sports;n=storage;h=host.somecompany.com;a=salt;t=tstamp;e=expiry;s=sig + * + * v: version number U1 or S1 (string) + * d: domain name (as passed by the client) or user for users + * n: service name (as passed by the client) or username + * h: hostname or IP address (string) + * a: random 8 byte salt value hex encoded + * t: timestamp when the token was generated + * e: expiry timestamp based on SIA configuration + * s: signature generated over the "v=U1;a=salt;...;e=expiry" string + * using Service's private Key for service tokens and ZMS service's + * private key for user tokens and y64 encoded + */ + + int idx = signedToken.indexOf(";s="); + if (idx != -1) { + unsignedToken = signedToken.substring(0, idx); + } + + for (String item : signedToken.split(";")) { + String [] kv = item.split("="); + if (kv.length == 2) { + switch (kv[0]) { + case "a": + salt = kv[1]; + break; + case "b": + authorizedServices = Arrays.asList(kv[1].split(",")); + break; + case "bk": + authorizedServiceKeyId = kv[1]; + break; + case "bn": + authorizedServiceName = kv[1]; + break; + case "bs": + authorizedServiceSignature = kv[1]; + break; + case "d": + domain = kv[1]; + break; + case "e": + expiryTime = Long.parseLong(kv[1]); + break; + case "h": + host = kv[1]; + break; + case "i": + ip = kv[1]; + break; + case "k": + keyId = kv[1]; + break; + case "n": + name = kv[1]; + break; + case "o": + originalRequestor = kv[1]; + break; + case "s": + signature = kv[1]; + break; + case "t": + timestamp = Long.parseLong(kv[1]); + break; + case "v": + version = kv[1]; + break; + case "z": + keyService = kv[1]; + break; + } + } + } + + /* the required attributes for the token are + * domain and roles. The signature will be verified + * during the authenticate phase but now we'll make + * sure that domain and roles are present + */ + + if (domain == null || domain.isEmpty()) { + throw new IllegalArgumentException("SignedToken does not contain required domain component"); + } + + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("SignedToken does not contain required name component"); + } + + this.signedToken = signedToken; + + if (LOG.isDebugEnabled()) { + LOG.debug("Values extracted from token " + + " version:" + version + + " domain:" + domain + + " service:" + name + + " host:" + host + + " ip: " + ip + + " id: " + keyId + + " keyServic: " + keyService + + " originalRequestor: " + originalRequestor + + " salt:" + salt + + " timestamp:" + timestamp + + " expiryTime:" + expiryTime + + " signature:" + signature); + if (authorizedServices != null) { + LOG.debug("Authorized service details from token " + + " authorizedServices:" + String.join(",", authorizedServices) + + " authorizedServiceName:" + authorizedServiceName + + " authorizedServiceKeyId:" + authorizedServiceKeyId + + " authorizedServiceSignature:" + authorizedServiceSignature); + } + } + } + + public void signForAuthorizedService(String authorizedServiceName, String authorizedServiceKeyId, + String privKey) throws CryptoException { + signForAuthorizedService(authorizedServiceName, authorizedServiceKeyId, Crypto.loadPrivateKey(privKey)); + } + + public void signForAuthorizedService(String authorizedServiceName, String authorizedServiceKeyId, + PrivateKey key) throws CryptoException { + + /* first let's make sure the authorized service is one of the + * listed service names in the PrincipalToken */ + + if (authorizedServices == null || !authorizedServices.contains(authorizedServiceName)) { + throw new IllegalArgumentException("Authorized Service is not valid for this token"); + } + + this.authorizedServiceKeyId = authorizedServiceKeyId; + StringBuilder tokenToSign = new StringBuilder(512); + tokenToSign.append(signedToken); + tokenToSign.append(";bk="); + tokenToSign.append(authorizedServiceKeyId); + + if (authorizedServices.size() > 1) { + + /* if the user has allowed multiple authorized services then we need + * to keep track of which one is re-signing this token and as such + * we'll store the service name as the value for the bn field */ + + this.authorizedServiceName = authorizedServiceName; + tokenToSign.append(";bn="); + tokenToSign.append(authorizedServiceName); + } + + authorizedServiceSignature = Crypto.sign(tokenToSign.toString(), key); + + /* now append our new signature to the token we just signed */ + + tokenToSign.append(";bs="); + tokenToSign.append(authorizedServiceSignature); + signedToken = tokenToSign.toString(); + } + + public boolean validateForAuthorizedService(String pubKey, StringBuilder errMsg) { + errMsg = errMsg == null ? new StringBuilder(512) : errMsg; + if (authorizedServiceSignature == null) { + errMsg.append("PrincipalToken:validateForAuthorizedService: token="). + append(unsignedToken). + append(" : missing data/signature component: public key="). + append(pubKey); + LOG.error(errMsg.toString()); + return false; + } + + int idx = signedToken.indexOf(";bs="); + if (idx == -1) { + errMsg.append("PrincipalToken:validateForAuthorizedService: token="). + append(unsignedToken).append(" : not signed by any authorized service"); + LOG.error(errMsg.toString()); + return false; + } + + String unsignedAuthorizedServiceToken = signedToken.substring(0, idx); + + if (pubKey == null) { + errMsg.append("PrincipalToken:validateForAuthorizedService: token="). + append(unsignedToken).append(" : No public key provided"); + LOG.error(errMsg.toString()); + return false; + } + + PublicKey pub = null; + boolean verified = false; // fail safe + try { + pub = Crypto.loadPublicKey(pubKey); + verified = Crypto.verify(unsignedAuthorizedServiceToken, pub, authorizedServiceSignature); + if (verified == false) { + errMsg.append("PrincipalToken:validateForAuthorizedService: token="). + append(unsignedToken).append(" : authentication failed: public key="). + append(pubKey); + LOG.error(errMsg.toString()); + } else if (LOG.isDebugEnabled()) { + LOG.debug("validateForAuthorizedService: Token: " + unsignedToken + + " - successfully authenticated"); + } + } catch (Exception e) { + errMsg.append("PrincipalToken:validateForAuthorizedService: token="). + append(unsignedToken). + append(" : authentication failed verifying signature: exc="). + append(e.getMessage()).append(" : public key=").append(pubKey); + LOG.error(errMsg.toString()); + } + + return verified; + } + + public boolean isValidAuthorizedServiceToken(StringBuilder errMsg) { + + /* we start our by checking if this is an authorized service token */ + errMsg = errMsg == null ? new StringBuilder(512) : errMsg; + if (authorizedServices == null) { + + /* if both the service name list and signature are not present + * then we have a standard principal token */ + + if (authorizedServiceSignature == null) { + return true; + } + + /* otherwise we have an invalid token without the signature */ + errMsg.append("PrincipalToken:isValidAuthorizedServiceToken: Invalid Token="). + append(unsignedToken). + append(" : Authorized Service Signature available without service name"); + LOG.error(errMsg.toString()); + return false; + } + + /* if we have an authorized service name then we must have a corresponding + * signature available in the token */ + + if (authorizedServiceSignature == null) { + errMsg.append("PrincipalToken:isValidAuthorizedServiceToken: Invalid Token="). + append(unsignedToken). + append(" : Missing signature for specified authorized service"); + LOG.error(errMsg.toString()); + return false; + } + + /* if we have a specific authorized service name specified then + * it must be present in our service list otherwise we must + * have a single entry in our list */ + + if (authorizedServiceName != null) { + if (!authorizedServices.contains(authorizedServiceName)) { + errMsg.append("PrincipalToken:isValidAuthorizedServiceToken: Invalid Token="). + append(unsignedToken). + append(" : Authorized service name=").append(authorizedServiceName). + append(" is not listed in the service list"); + LOG.error(errMsg.toString()); + return false; + } + } else if (authorizedServices.size() != 1) { + errMsg.append("PrincipalToken:isValidAuthorizedServiceToken: Invalid Token="). + append(unsignedToken). + append(" : No service name and Authorized service list contains multiple entries"); + LOG.error(errMsg.toString()); + return false; + } + + return true; + } + + public String getName() { + return name; + } + + public String getKeyService() { + return keyService; + } + + public String getOriginalRequestor() { + return originalRequestor; + } + + public List getAuthorizedServices() { + return authorizedServices; + } + + public String getAuthorizedServiceName() { + return authorizedServiceName; + } + + public String getAuthorizedServiceKeyId() { + return authorizedServiceKeyId; + } + + public String getAuthorizedServiceSignature() { + return authorizedServiceSignature; + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/RoleToken.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/RoleToken.java new file mode 100644 index 00000000000..6ac17740e47 --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/RoleToken.java @@ -0,0 +1,327 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.token; + +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.util.Crypto; + +public class RoleToken extends Token { + + protected List roles = null; + private String principal = null; + private String proxyUser = null; + private boolean domainCompleteRoleSet = false; + + private static final Logger LOG = LoggerFactory.getLogger(RoleToken.class); + + public static class Builder { + + // required attributes + private String domain = null; + private List roles = null; + private String version = null; + private String principal = null; + private String proxyUser = null; + private boolean domainCompleteRoleSet = false; + + // optional attributes with default values + private String salt = Crypto.randomSalt(); + private String host = null; + private String ip = null; + private String keyId = "0"; + private long expirationWindow = 3600; + private long issueTime = 0; + + // Note that it is expected that the Strings in roles should already be lowercased. + public Builder(String version, String domain, List roles) { + + if (version == null || domain == null || roles == null) { + throw new IllegalArgumentException("version, domain and roles parameters must not be null."); + } + + if (version.isEmpty() || domain.isEmpty() || roles.isEmpty()) { + throw new IllegalArgumentException("version, domain and roles parameters must have values."); + } + + this.version = version; + this.domain = domain; + this.roles = roles; + } + + public Builder principal(String value) { + this.principal = value; + return this; + } + + public Builder host(String value) { + this.host = value; + return this; + } + + public Builder salt(String value) { + this.salt = value; + return this; + } + + public Builder ip(String value) { + this.ip = value; + return this; + } + + public Builder keyId(String value) { + this.keyId = value; + return this; + } + + public Builder proxyUser(String value) { + this.proxyUser = value; + return this; + } + + public Builder issueTime(long value) { + this.issueTime = value; + return this; + } + + public Builder expirationWindow(long value) { + this.expirationWindow = value; + return this; + } + + public Builder domainCompleteRoleSet(boolean value) { + this.domainCompleteRoleSet = value; + return this; + } + + public RoleToken build() { + return new RoleToken(this); + } + } + + private RoleToken(Builder builder) { + + this.version = builder.version; + this.domain = builder.domain; + this.roles = builder.roles; + this.host = builder.host; + this.salt = builder.salt; + this.keyId = builder.keyId; + this.ip = builder.ip; + this.principal = builder.principal; + this.proxyUser = builder.proxyUser; + this.domainCompleteRoleSet = builder.domainCompleteRoleSet; + + super.setTimeStamp(builder.issueTime, builder.expirationWindow); + + StringBuilder strBuilder = new StringBuilder(defaultBuilderBufSize); + + strBuilder.append("v="); + strBuilder.append(version); + strBuilder.append(";d="); + strBuilder.append(domain); + strBuilder.append(";r="); + + int i = 0; + for (String role : roles) { + strBuilder.append(role); + if (++i != roles.size()) { + strBuilder.append(","); + } + } + + if (domainCompleteRoleSet) { + strBuilder.append(";c=1"); + } + + if (principal != null && !principal.isEmpty()) { + strBuilder.append(";p="); + strBuilder.append(principal); + } + + if (host != null && !host.isEmpty()) { + strBuilder.append(";h="); + strBuilder.append(host); + } + + if (proxyUser != null && !proxyUser.isEmpty()) { + strBuilder.append(";proxy="); + strBuilder.append(proxyUser); + } + + strBuilder.append(";a="); + strBuilder.append(salt); + strBuilder.append(";t="); + strBuilder.append(timestamp); + strBuilder.append(";e="); + strBuilder.append(expiryTime); + strBuilder.append(";k="); + strBuilder.append(keyId); + if (ip != null && !ip.isEmpty()) { + strBuilder.append(";i="); + strBuilder.append(ip); + } + + unsignedToken = strBuilder.toString(); + + if (LOG.isDebugEnabled()) { + LOG.debug("RoleToken created: " + unsignedToken); + } + } + + public RoleToken(String signedToken) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Constructing RoleToken with input string: " + signedToken); + } + + if (signedToken == null || signedToken.isEmpty()) { + throw new IllegalArgumentException("Input String signedToken must not be empty"); + } + + /** + * first we need to extract data and signature parts + * the signature is always at the end of the token. + * The format for the Token is as follows: + * + * v=Z1;d=sports;r=role1,role2;a=salt;t=tstamp;e=expiry;k=1;s=sig + * + * v: version number Z1 (string) + * d: domain name where the roles are valid for + * r: list of comma separated roles + * c: the list of roles is complete in domain + * p: principal that got the token issued for + * a: random 8 byte salt value hex encoded + * t: timestamp when the token was generated + * h: host that issued this role token + * e: expiry timestamp based on SIA configuration + * k: identifier - either version or zone name + * s: signature generated over the "v=Z1;a=salt;...;e=expiry" string + * using Service's private Key and y64 encoded + * proxy: request was done by this authorized proxy user + */ + + int idx = signedToken.indexOf(";s="); + if (idx != -1) { + unsignedToken = signedToken.substring(0, idx); + } + + String roleNames = null; + for (String item : signedToken.split(";")) { + String [] kv = item.split("="); + if (kv.length == 2) { + switch (kv[0]) { + case "a": + salt = kv[1]; + break; + case "c": + if (Integer.parseInt(kv[1]) == 1) { + domainCompleteRoleSet = true; + } + break; + case "d": + domain = kv[1]; + break; + case "e": + expiryTime = Long.parseLong(kv[1]); + break; + case "h": + host = kv[1]; + break; + case "i": + ip = kv[1]; + break; + case "k": + keyId = kv[1]; + break; + case "p": + principal = kv[1]; + break; + case "r": + roleNames = kv[1]; + break; + case "s": + signature = kv[1]; + break; + case "t": + timestamp = Long.parseLong(kv[1]); + break; + case "proxy": + proxyUser = kv[1]; + break; + case "v": + version = kv[1]; + break; + } + } + } + + /* the required attributes for the token are + * domain and roles. The signature will be verified + * during the authenticate phase but now we'll make + * sure that domain and roles are present + */ + + if (domain == null || domain.isEmpty()) { + throw new IllegalArgumentException("SignedToken does not contain required domain component"); + } + + if (roleNames == null || roleNames.isEmpty()) { + throw new IllegalArgumentException("SignedToken does not contain required roles component"); + } + + roles = Arrays.asList(roleNames.split(",")); + + this.signedToken = signedToken; + + if (LOG.isDebugEnabled()) { + LOG.debug("Values extracted from token " + + " version:" + version + + " domain:" + domain + + " roles:" + roleNames + + " principal:" + principal + + " host:" + host + + " salt:" + salt + + " timestamp:" + timestamp + + " expiryTime:" + expiryTime + + " domainCompleteRoleSet" + domainCompleteRoleSet + + " keyId:" + keyId + + " ip:" + ip + + " proxyUser: " + proxyUser + + " signature:" + signature); + } + } + + public String getPrincipal() { + return principal; + } + + public List getRoles() { + return roles; + } + + public String getProxyUser() { + return proxyUser; + } + + public boolean getDomainCompleteRoleSet() { + return domainCompleteRoleSet; + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/Token.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/Token.java new file mode 100644 index 00000000000..ab6def379a8 --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/Token.java @@ -0,0 +1,205 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.token; + +import java.security.PrivateKey; +import java.security.PublicKey; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.auth.util.CryptoException; + +public class Token { + private static final Logger LOG = LoggerFactory.getLogger(Token.class); + + protected final int defaultBuilderBufSize = 512; + + protected String unsignedToken = null; + protected String signedToken = null; + protected String version = null; + protected String salt = null; + protected String host = null; + protected String ip = null; + protected String domain = null; + protected String signature = null; + protected String keyId = "0"; + protected long expiryTime = 0; + protected long timestamp = 0; + protected String digestAlgorithm = Crypto.SHA256; + + public void sign(String privKey) throws CryptoException { + sign(Crypto.loadPrivateKey(privKey)); + } + + public void sign(PrivateKey key) throws CryptoException { + signature = Crypto.sign(unsignedToken, key, digestAlgorithm); + signedToken = unsignedToken + ";s=" + signature; + } + + public void setTimeStamp(long issueTime, long expirationWindow) { + timestamp = (issueTime > 0) ? issueTime : System.currentTimeMillis() / 1000; + expiryTime = timestamp + expirationWindow; + } + + public boolean validate(String pubKey, int allowedOffset) { + return validate(pubKey, allowedOffset, null); + } + + public boolean validate(String pubKey, int allowedOffset, StringBuilder errMsg) { + + errMsg = errMsg == null ? new StringBuilder(512) : errMsg; + if (pubKey == null) { + errMsg.append("Token:validate: token=").append(unsignedToken). + append(" : No public key provided"); + LOG.error(errMsg.toString()); + return false; + } + + PublicKey publicKey = null; + try { + publicKey = Crypto.loadPublicKey(pubKey); + } catch (Exception e) { + errMsg.append("Token:validate: token=").append(unsignedToken). + append(" : unable to load public key due to Exception="). + append(e.getMessage()); + LOG.error(errMsg.toString()); + return false; + } + + return validate(publicKey, allowedOffset, errMsg); + } + + public boolean validate(PublicKey publicKey, int allowedOffset, StringBuilder errMsg) { + + errMsg = errMsg == null ? new StringBuilder(512) : errMsg; + if (unsignedToken == null || signature == null) { + errMsg.append("Token:validate: token=").append(unsignedToken). + append(" : missing data/signature component"); + LOG.error(errMsg.toString()); + return false; + } + + if (publicKey == null) { + errMsg.append("Token:validate: token=").append(unsignedToken). + append(" : No public key provided"); + LOG.error(errMsg.toString()); + return false; + } + + long now = System.currentTimeMillis() / 1000; + + /** + * make sure the token does not have a timestamp in the future + * we'll allow the configured offset between servers + */ + + if (timestamp != 0 && timestamp - allowedOffset > now) { + errMsg.append("Token:validate: token=").append(unsignedToken). + append(" : has future timestamp=").append(timestamp). + append(" : current time=").append(now); + LOG.error(errMsg.toString()); + return false; + } + + if (expiryTime != 0 && expiryTime < now) { + errMsg.append("Token:validate: token=").append(unsignedToken). + append(" : has expired time=").append(expiryTime). + append(" : current time=").append(now); + LOG.error(errMsg.toString()); + return false; + } + + boolean verified = false; // fail safe + try { + verified = Crypto.verify(unsignedToken, publicKey, signature, digestAlgorithm); + if (verified == false) { + errMsg.append("Token:validate: token=").append(unsignedToken). + append(" : authentication failed"); + LOG.error(errMsg.toString()); + } else if (LOG.isDebugEnabled()) { + LOG.debug("validate: Token successfully authenticated"); + } + } catch (Exception e) { + errMsg.append("Token:validate: token=").append(unsignedToken). + append(" : verify signature failed due to Exception="). + append(e.getMessage()); + LOG.error(errMsg.toString()); + } + + return verified; + } + + public String getVersion() { + return version; + } + + public String getSalt() { + return salt; + } + + public String getHost() { + return host; + } + + public String getDomain() { + return domain; + } + + public String getSignature() { + return signature; + } + + public long getTimestamp() { + return timestamp; + } + + public long getExpiryTime() { + return expiryTime; + } + + public String getSignedToken() { + return signedToken; + } + + public String getKeyId() { + return keyId; + } + + public String getIP() { + return ip; + } + + public String getUnsignedToken() { + return unsignedToken; + } + + /** + * Helper method to parse a credential to remove the signature from the + * raw credential string. Returning the unsigned credential. + * @param credential full token credentials including signature + * @return credentials without the signature + **/ + public static String getUnsignedToken(String credential) { + int idx = credential.indexOf(";s="); + if (idx != -1) { + credential = credential.substring(0, idx); + } + + return credential; + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/Crypto.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/Crypto.java new file mode 100644 index 00000000000..41e9ec3946d --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/Crypto.java @@ -0,0 +1,892 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.util; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.MessageDigest; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.rdl.*; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.ExtendedKeyUsage; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.pkcs.Attribute; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x9.ECNamedCurveTable; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSProcessable; +import org.bouncycastle.cms.CMSProcessableByteArray; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.SignerInformation; +import org.bouncycastle.cms.SignerInformationStore; +import org.bouncycastle.cms.SignerInformationVerifier; +import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jce.X509KeyUsage; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.jce.spec.ECPrivateKeySpec; +import org.bouncycastle.openssl.PEMException; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.InputDecryptorProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.bouncycastle.pkcs.PKCSException; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; +import org.bouncycastle.util.encoders.Base64; + +public class Crypto { + + private static final Logger LOG = LoggerFactory.getLogger(Crypto.class); + private static final String RSA = "RSA"; + private static final String RSA_SHA1 = "SHA1withRSA"; + private static final String RSA_SHA256 = "SHA256withRSA"; + + private static final String ECDSA = "ECDSA"; + private static final String ECDSA_SHA1 = "SHA1withECDSA"; + private static final String ECDSA_SHA256 = "SHA256withECDSA"; + + public static final String SHA1 = "SHA1"; + public static final String SHA256 = "SHA256"; + + private static final String BC_PROVIDER = "BC"; + + static final SecureRandom RANDOM; + static { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + SecureRandom r = null; + try { + r = SecureRandom.getInstance("NativePRNGNonBlocking"); + } catch (NoSuchAlgorithmException nsa) { + r = new SecureRandom(); + } + + RANDOM = r; + // force seeding. + RANDOM.nextBytes(new byte[] { 8 }); + } + + /** + * Return a signature for the specific structured data with HmacSHA256 algorithm, + * using the provided secret. + * @param data structured data + * @param sharedSecret provided secret + * @return signature for the data + * @throws CryptoException for any issues with provider/algorithm/signature/key + */ + public static String hmac(Struct data, String sharedSecret) throws CryptoException { + return hmac(hashableString(data), sharedSecret); + } + + /** + * Calculate the SHA256withRSA signature for the specific structured data, using the provided private key. + * @param data structured data to sign + * @param key the RSA private key to sign with + * @return a ybase64 encoded signature + * @throws CryptoException for any issues with provider/algorithm/signature/key + */ + public static String sign(Struct data, PrivateKey key) throws CryptoException { + return sign(hashableString(data), key); + } + + /** + * Verify the SHA256withRSA signature for the specific structured data, using the provided public key. + * @param data structured data that was signed + * @param key the RSA public key corresponding to the signing key + * @param signature the ybase64 encoded signature to check + * @return true if the data signature can be verified with the given public key + * @throws CryptoException for any issues with provider/algorithm/signature/key + */ + public static boolean verify(Struct data, PublicKey key, String signature) throws CryptoException { + return verify(hashableString(data), key, signature); + } + + private static Object canonical(Object obj) { + if (obj != null) { + if (obj instanceof Struct) { + Struct s = (Struct) obj; + Struct s2 = new Struct(); + for (String k : s.sortedNames()) { + s2.put(k, canonical(s.get(k))); + } + return s2; + } else if (obj instanceof Array) { + Array a = new Array(); + for (Object o : (Array) obj) { + a.add(canonical(o)); + } + return a; + } + } + return obj; + } + + /** + * Make a copy of the data in canonical form. This means the field names are sorted, + * (recursively), so the same data (from a JSON point of view) will produce the same + * string. + * @param data structured data to normalize + * @return the canonical form for the data + */ + public static String hashableString(Struct data) { + return JSON.string(canonical(data)); + } + + /** + * Sign the message with the shared secret using HmacSHA256 + * The result is a ybase64 (url safe) string. + * @param message the UTF-8 string to be signed + * @param sharedSecret the secret to sign with + * @return the ybase64 representation of the signature. + * @throws CryptoException for any issues with provider/algorithm/signature/key + */ + public static String hmac(String message, String sharedSecret) throws CryptoException { + //this has not been optimized! + String method = "HmacSHA256"; + byte [] bsig = null; + try { + + javax.crypto.Mac hmac = javax.crypto.Mac.getInstance(method); + javax.crypto.spec.SecretKeySpec secretKey = new javax.crypto.spec.SecretKeySpec(utf8Bytes(sharedSecret), method); + hmac.init(secretKey); + bsig = hmac.doFinal(message.getBytes()); + + } catch (NoSuchAlgorithmException e) { + LOG.error("hmac: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); + throw new CryptoException(e); + } catch (InvalidKeyException e) { + LOG.error("hmac: Caught InvalidKeyException, incorrect key type is being used."); + throw new CryptoException(e); + } + return ybase64(bsig); + } + + static String getSignatureAlgorithm(String keyAlgorithm) throws NoSuchAlgorithmException { + return getSignatureAlgorithm(keyAlgorithm, SHA256); + } + + static String getSignatureAlgorithm(String keyAlgorithm, String digestAlgorithm) throws NoSuchAlgorithmException { + + String signatureAlgorithm = null; + switch (keyAlgorithm) { + case RSA: + if (SHA256.equals(digestAlgorithm)) { + signatureAlgorithm = RSA_SHA256; + } else if (SHA1.equals(digestAlgorithm)) { + signatureAlgorithm = RSA_SHA1; + } + break; + case ECDSA: + if (SHA256.equals(digestAlgorithm)) { + signatureAlgorithm = ECDSA_SHA256; + } else if (SHA1.equals(digestAlgorithm)) { + signatureAlgorithm = ECDSA_SHA1; + } + break; + } + + if (signatureAlgorithm == null) { + LOG.error("getSignatureAlgorithm: Unknown key algorithm: " + keyAlgorithm + + " digest algorithm: " + digestAlgorithm); + throw new NoSuchAlgorithmException(); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Signature Algorithm: " + signatureAlgorithm); + } + + return signatureAlgorithm; + } + + /** + * Sign the text with with given digest algorithm and private key. Returns the ybase64 encoding of it. + * @param message the message to sign, as a UTF8 string + * @param key the private key to sign with + * @param digestAlgorithm supported values SHA1 and SHA256 + * @return the ybase64 encoded signature for the data + * @throws CryptoException for any issues with provider/algorithm/signature/key + */ + public static String sign(String message, PrivateKey key, String digestAlgorithm) throws CryptoException { + try { + byte [] sig = null; + String signatureAlgorithm = getSignatureAlgorithm(key.getAlgorithm(), digestAlgorithm); + java.security.Signature signer = java.security.Signature.getInstance(signatureAlgorithm, BC_PROVIDER); + signer.initSign(key); + signer.update(utf8Bytes(message)); + sig = signer.sign(); + return ybase64(sig); + } catch (NoSuchProviderException e) { + LOG.error("sign: Caught NoSuchProviderException, check to make sure the provider is loaded correctly."); + throw new CryptoException(e); + } catch (NoSuchAlgorithmException e) { + LOG.error("sign: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); + throw new CryptoException(e); + } catch (SignatureException e) { + LOG.error("sign: Caught SignatureException."); + throw new CryptoException(e); + } catch (InvalidKeyException e) { + LOG.error("sign: Caught InvalidKeyException, incorrect key type is being used."); + throw new CryptoException(e); + } + } + + /** + * Sign the text with with SHA-256 and the private key. Returns the ybase64 encoding of it. + * @param message the message to sign, as a UTF8 string + * @param key the private key to sign with + * @return the ybase64 encoded signature for the data + * @throws CryptoException for any issues with provider/algorithm/signature/key + */ + public static String sign(String message, PrivateKey key) throws CryptoException { + return sign(message, key, SHA256); + } + + /** + * Verify the signed data with given digest algorithm and the private key against the ybase64 encoded signature. + * @param message the message to sign, as a UTF8 string + * @param key the public key corresponding to the signing key + * @param signature the ybase64 encoded signature for the data + * @param digestAlgorithm supported values SHA1 and SHA256 + * @return true if the message was indeed signed by the signature. + * @throws CryptoException for any issues with provider/algorithm/signature/key + */ + public static boolean verify(String message, PublicKey key, String signature, + String digestAlgorithm) throws CryptoException { + try { + byte [] sig = ybase64Decode(signature); + String signatureAlgorithm = getSignatureAlgorithm(key.getAlgorithm(), digestAlgorithm); + java.security.Signature signer = java.security.Signature.getInstance(signatureAlgorithm, BC_PROVIDER); + signer.initVerify(key); + signer.update(utf8Bytes(message)); + return signer.verify(sig); + } catch (NoSuchProviderException e) { + LOG.error("verify: Caught NoSuchProviderException, check to make sure the provider is loaded correctly."); + throw new CryptoException(e); + } catch (NoSuchAlgorithmException e) { + LOG.error("verify: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); + throw new CryptoException(e); + } catch (SignatureException e) { + LOG.error("verify: Caught SignatureException."); + throw new CryptoException(e); + } catch (InvalidKeyException e) { + LOG.error("verify: Caught InvalidKeyException, invalid key type is being used."); + throw new CryptoException(e); + } + } + + /** + * Verify the signed data with SHA-256 and private key against the ybase64 encoded signature. + * @param message the message to sign, as a UTF8 string + * @param key the public key corresponding to the signing key + * @param signature the ybase64 encoded signature for the data + * @return true if the message was indeed signed by the signature. + * @throws CryptoException for any issues with provider/algorithm/signature/key + */ + public static boolean verify(String message, PublicKey key, String signature) throws CryptoException { + return verify(message, key, signature, SHA256); + } + + static String utf8String(byte [] b) { + return new String(b, StandardCharsets.UTF_8); + } + + static byte [] utf8Bytes(String s) { + return s.getBytes(StandardCharsets.UTF_8); + } + + public static byte [] sha256(byte [] data) throws CryptoException { + MessageDigest sha256; + try { + sha256 = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + LOG.error("sha256: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); + throw new CryptoException(e); + } + return sha256.digest(data); + } + + public static byte [] sha256(String text) throws CryptoException { + return sha256(utf8Bytes(text)); + } + + /** + * ybase64 is url-safe base64 encoding, using Y's unique convention. + * The industry standard urlsafe solution is ("+/=" => "-_."). + * The Y! convention is ("+/=" => "._-"). + * @param data the data to encode + * @return the ybase64-encoded data as a String + */ + public static String ybase64(byte [] data) { + return utf8String(YBase64.encode(data)); + } + + /** + * ybase64 is url-safe base64 encoding, using Y's unique convention. + * The industry standard urlsafe solution is ("+/=" => "-_."). + * The Y! convention is ("+/=" => "._-"). + * @param b64 the ybase64-encoded data + * @return the decoded data + */ + public static byte [] ybase64Decode(String b64) { + return YBase64.decode(utf8Bytes(b64)); + } + + public static String ybase64DecodeString(String b64) { + return utf8String(ybase64Decode(b64)); + } + + public static X509Certificate loadX509Certificate(File certFile) throws CryptoException { + try (FileReader fileReader = new FileReader(certFile)) { + return loadX509Certificate(fileReader); + } catch (FileNotFoundException e) { + LOG.error("loadX509Certificate: Caught FileNotFoundException while attempting to load certificate for file: " + + certFile.getAbsolutePath()); + throw new CryptoException(e); + } catch (IOException e) { + LOG.error("loadX509Certificate: Caught IOException while attempting to load certificate for file: " + + certFile.getAbsolutePath()); + throw new CryptoException(e); + } + } + + public static X509Certificate loadX509Certificate(String pemEncoded) throws CryptoException { + return Crypto.loadX509Certificate(new StringReader(pemEncoded)); + } + + public static X509Certificate loadX509Certificate(Reader reader) throws CryptoException { + try (PEMParser pemParser = new PEMParser(reader)) { + Object pemObj = pemParser.readObject(); + if (pemObj instanceof X509Certificate) { + return (X509Certificate) pemObj; + } else if (pemObj instanceof X509CertificateHolder) { + try { + return new JcaX509CertificateConverter() + .setProvider(BC_PROVIDER) + .getCertificate((X509CertificateHolder) pemObj); + } catch (CertificateException ex) { + LOG.error("loadX509Certificate: Caught CertificateException, unable to parse X509 certficate", ex); + throw new CryptoException(ex); + } + } + } catch (IOException ex) { + LOG.error("loadX509Certificate: Caught IOException, unable to parse X509 certficate", ex); + throw new CryptoException(ex); + } + + return null; + } + + public static PublicKey loadPublicKey(String pemEncoded) throws CryptoException { + return Crypto.loadPublicKey(new StringReader(pemEncoded)); + } + + public static PublicKey loadPublicKey(Reader r) throws CryptoException { + try (org.bouncycastle.openssl.PEMParser pemReader = new org.bouncycastle.openssl.PEMParser(r)) { + PublicKey pubKey = null; + Object pemObj = pemReader.readObject(); + JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); + SubjectPublicKeyInfo keyInfo = null; + X9ECParameters ecParam = null; + + if (pemObj instanceof ASN1ObjectIdentifier) { + + // make sure this is EC Parameter we're handling. In which case + // we'll store it and read the next object which should be our + // EC Public Key + + ASN1ObjectIdentifier ecOID = (ASN1ObjectIdentifier) pemObj; + ecParam = ECNamedCurveTable.getByOID(ecOID); + if (ecParam == null) { + throw new PEMException("Unable to find EC Parameter for the given curve oid: " + + ((ASN1ObjectIdentifier) pemObj).getId()); + } + + pemObj = pemReader.readObject(); + } else if (pemObj instanceof X9ECParameters) { + ecParam = (X9ECParameters) pemObj; + pemObj = pemReader.readObject(); + } + + if (pemObj instanceof org.bouncycastle.cert.X509CertificateHolder) { + keyInfo = ((org.bouncycastle.cert.X509CertificateHolder) pemObj).getSubjectPublicKeyInfo(); + } else { + keyInfo = (SubjectPublicKeyInfo) pemObj; + } + pubKey = pemConverter.getPublicKey(keyInfo); + + if (ecParam != null && ECDSA.equals(pubKey.getAlgorithm())) { + ECParameterSpec ecSpec = new ECParameterSpec(ecParam.getCurve(), ecParam.getG(), + ecParam.getN(), ecParam.getH(), ecParam.getSeed()); + KeyFactory keyFactory = KeyFactory.getInstance(ECDSA, BC_PROVIDER); + ECPublicKeySpec keySpec = new ECPublicKeySpec(((BCECPublicKey) pubKey).getQ(), ecSpec); + pubKey = (PublicKey) keyFactory.generatePublic(keySpec); + } + return pubKey; + } catch (PEMException e) { + throw new CryptoException(e); + } catch (NoSuchProviderException e) { + LOG.error("loadPublicKey: Caught NoSuchProviderException, check to make sure the provider is loaded correctly."); + throw new CryptoException(e); + } catch (NoSuchAlgorithmException e) { + LOG.error("loadPublicKey: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); + throw new CryptoException(e); + } catch (InvalidKeySpecException e) { + LOG.error("loadPublicKey: Caught InvalidKeySpecException, invalid key spec is being used."); + throw new CryptoException("InvalidKeySpecException"); + } catch (IOException e) { + throw new CryptoException(e); + } + } + + public static PublicKey loadPublicKey(File f) throws CryptoException { + try (FileReader fileReader = new FileReader(f)) { + return loadPublicKey(fileReader); + } catch (FileNotFoundException e) { + LOG.error("loadPublicKey: Caught FileNotFoundException while attempting to load public key for file: " + + f.getAbsolutePath()); + throw new CryptoException(e); + } catch (IOException e) { + LOG.error("loadPublicKey: Caught IOException while attempting to load public key for file: " + + f.getAbsolutePath()); + throw new CryptoException(e); + } + } + + public static PrivateKey loadPrivateKey(String pemEncoded) throws CryptoException { + return Crypto.loadPrivateKey(new StringReader(pemEncoded), null); + } + + public static PrivateKey loadPrivateKey(Reader reader) throws CryptoException { + return Crypto.loadPrivateKey(reader, null); + } + + public static PrivateKey loadPrivateKey(File file) throws CryptoException { + return Crypto.loadPrivateKey(file, null); + } + + public static PrivateKey loadPrivateKey(File file, String pwd) throws CryptoException { + try (java.io.FileReader fileReader = new java.io.FileReader(file)) { + return loadPrivateKey(fileReader, pwd); + } catch (FileNotFoundException e) { + LOG.error("loadPrivateKey: Caught FileNotFoundException while attempting to load private key for file: " + + file.getAbsolutePath()); + throw new CryptoException(e); + } catch (IOException e) { + LOG.error("loadPrivateKey: Caught IOException while attempting to load private key for file: " + + file.getAbsolutePath()); + throw new CryptoException(e); + } + } + + public static PrivateKey loadPrivateKey(String pemEncoded, String pwd) throws CryptoException { + return Crypto.loadPrivateKey(new StringReader(pemEncoded), pwd); + } + + public static PrivateKey loadPrivateKey(Reader reader, String pwd) throws CryptoException { + + try (PEMParser pemReader = new PEMParser(reader)) { + PrivateKey privKey = null; + X9ECParameters ecParam = null; + + Object pemObj = pemReader.readObject(); + + if (pemObj instanceof ASN1ObjectIdentifier) { + + // make sure this is EC Parameter we're handling. In which case + // we'll store it and read the next object which should be our + // EC Private Key + + ASN1ObjectIdentifier ecOID = (ASN1ObjectIdentifier) pemObj; + ecParam = ECNamedCurveTable.getByOID(ecOID); + if (ecParam == null) { + throw new PEMException("Unable to find EC Parameter for the given curve oid: " + + ((ASN1ObjectIdentifier) pemObj).getId()); + } + + pemObj = pemReader.readObject(); + + } else if (pemObj instanceof X9ECParameters) { + + ecParam = (X9ECParameters) pemObj; + pemObj = pemReader.readObject(); + } + + if (pemObj instanceof PEMKeyPair) { + + PrivateKeyInfo pKeyInfo = ((PEMKeyPair) pemObj).getPrivateKeyInfo(); + JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); + privKey = pemConverter.getPrivateKey(pKeyInfo); + + } else if (pemObj instanceof PKCS8EncryptedPrivateKeyInfo) { + + PKCS8EncryptedPrivateKeyInfo pKeyInfo = (PKCS8EncryptedPrivateKeyInfo) pemObj; + if (pwd == null) { + throw new CryptoException("No password specified to decrypt encrypted private key"); + } + + // Decrypt the private key with the specified password + + InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder() + .setProvider(BC_PROVIDER).build(pwd.toCharArray()); + + PrivateKeyInfo privateKeyInfo = pKeyInfo.decryptPrivateKeyInfo(pkcs8Prov); + JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); + privKey = pemConverter.getPrivateKey(privateKeyInfo); + } + + // if our private key is EC type and we have parameters specified + // then we need to set it accordingly + + if (ecParam != null && ECDSA.equals(privKey.getAlgorithm())) { + ECParameterSpec ecSpec = new ECParameterSpec(ecParam.getCurve(), ecParam.getG(), + ecParam.getN(), ecParam.getH(), ecParam.getSeed()); + KeyFactory keyFactory = KeyFactory.getInstance(ECDSA, BC_PROVIDER); + ECPrivateKeySpec keySpec = new ECPrivateKeySpec(((BCECPrivateKey) privKey).getS(), ecSpec); + privKey = (PrivateKey) keyFactory.generatePrivate(keySpec); + } + + return privKey; + + } catch (PEMException e) { + LOG.error("loadPrivateKey: Caught PEMException, problem with format of key detected."); + throw new CryptoException(e); + } catch (NoSuchProviderException e) { + LOG.error("loadPrivateKey: Caught NoSuchProviderException, check to make sure the provider is loaded correctly."); + throw new CryptoException(e); + } catch (NoSuchAlgorithmException e) { + LOG.error("loadPrivateKey: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); + throw new CryptoException(e); + } catch (InvalidKeySpecException e) { + LOG.error("loadPrivateKey: Caught InvalidKeySpecException, invalid key spec is being used."); + throw new CryptoException(e); + } catch (OperatorCreationException e) { + LOG.error("loadPrivateKey: Caught OperatorCreationException when creating JceOpenSSLPKCS8DecryptorProviderBuilder."); + throw new CryptoException(e); + } catch (PKCSException e) { + LOG.error("loadPrivateKey: Caught PKCSException when decrypting private key."); + throw new CryptoException(e); + } catch (IOException e) { + LOG.error("loadPrivateKey: Caught IOException, while trying to read key."); + throw new CryptoException(e); + } + } + + public static String randomSalt() { + long v = RANDOM.nextLong(); + return Long.toHexString(v); + } + + public static String encodedFile(File f) { + try (FileInputStream in = new FileInputStream(f)) { + byte [] buf = new byte[(int) f.length()]; + in.read(buf); + return ybase64(buf); + } catch (FileNotFoundException e) { + LOG.error("encodedFile: Caught FileNotFoundException while attempting to read encoded file: " + + f.getAbsolutePath()); + throw new RuntimeException(e); + } catch (IOException e) { + LOG.error("encodedFile: Caught IOException while attempting to read encoded file: " + + f.getAbsolutePath()); + throw new RuntimeException(e); + } + } + + public static String encodedFile(FileInputStream is) { + try { + byte [] buf = new byte[4096]; + int readBytes = 0; + String contents = null; + while ((readBytes = is.read(buf)) > 0) { + if (contents == null) { + contents = new String(buf, 0, readBytes - 1); + } else { + contents = contents.concat(new String(buf, 0, readBytes - 1)); + } + } + return ybase64(utf8Bytes(contents)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static PKCS10CertificationRequest getPKCS10CertRequest(String csr) { + + if (csr == null || csr.isEmpty()) { + LOG.error("getPKCS10CertRequest: CSR is null or empty"); + throw new CryptoException("CSR is null or empty"); + } + + try { + Reader csrReader = new StringReader(csr); + try (PEMParser pemParser = new PEMParser(csrReader)) { + Object pemObj = pemParser.readObject(); + if (pemObj instanceof PKCS10CertificationRequest) { + return (PKCS10CertificationRequest) pemObj; + } + } + } catch (IOException ex) { + LOG.error("getPKCS10CertRequest: unable to parse csr", ex); + throw new CryptoException(ex); + } + + return null; + } + + public static X509Certificate generateX509Certificate(PKCS10CertificationRequest certReq, PrivateKey caPrivateKey, + X509Certificate caCertificate, int validityTimeout, boolean basicConstraints) { + + // set validity for the given number of minutes from now + + Date notBefore = new Date(); + Calendar cal = Calendar.getInstance(); + cal.setTime(notBefore); + cal.add(Calendar.MINUTE, validityTimeout); + Date notAfter = cal.getTime(); + + // Generate self-signed certificate + + X509Certificate cert = null; + try { + JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = new JcaPKCS10CertificationRequest(certReq); + PublicKey publicKey = jcaPKCS10CertificationRequest.getPublicKey(); + + X509v3CertificateBuilder caBuilder = new JcaX509v3CertificateBuilder( + caCertificate, + BigInteger.valueOf(System.currentTimeMillis()), + notBefore, + notAfter, + certReq.getSubject(), + publicKey) + .addExtension(Extension.basicConstraints, false, + new BasicConstraints(basicConstraints)) + .addExtension(Extension.keyUsage, true, + new X509KeyUsage(X509KeyUsage.digitalSignature | X509KeyUsage.keyEncipherment)) + .addExtension(Extension.extendedKeyUsage, true, + new ExtendedKeyUsage(new KeyPurposeId[]{ KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth })); + + // see if we have the dns/rfc822/ip address extensions specified in the csr + + ArrayList altNames = new ArrayList<>(); + Attribute[] certAttributes = jcaPKCS10CertificationRequest.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest); + if (certAttributes != null && certAttributes.length > 0) { + for (Attribute attribute : certAttributes) { + Extensions extensions = Extensions.getInstance(attribute.getAttrValues().getObjectAt(0)); + GeneralNames gns = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName); + if (gns == null) { + continue; + } + GeneralName[] names = gns.getNames(); + for (int i = 0; i < names.length; i++) { + switch (names[i].getTagNo()) { + case GeneralName.dNSName: + case GeneralName.iPAddress: + case GeneralName.rfc822Name: + altNames.add(names[i]); + break; + } + } + } + if (!altNames.isEmpty()) { + caBuilder.addExtension(Extension.subjectAlternativeName, false, + new GeneralNames(altNames.toArray(new GeneralName[altNames.size()]))); + } + } + + String signatureAlgorithm = getSignatureAlgorithm(caPrivateKey.getAlgorithm(), SHA256); + ContentSigner caSigner = new JcaContentSignerBuilder(signatureAlgorithm) + .setProvider(BC_PROVIDER).build(caPrivateKey); + + JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER); + cert = converter.getCertificate(caBuilder.build(caSigner)); + + } catch (CertificateException ex) { + LOG.error("generateX509Certificate: Caught CertificateException when generating certificate: " + + ex.getMessage()); + throw new CryptoException(ex); + } catch (OperatorCreationException ex) { + LOG.error("generateX509Certificate: Caught OperatorCreationException when creating JcaContentSignerBuilder: " + + ex.getMessage()); + throw new CryptoException(ex); + } catch (InvalidKeyException ex) { + LOG.error("generateX509Certificate: Caught InvalidKeySpecException, invalid key spec is being used: " + + ex.getMessage()); + throw new CryptoException(ex); + } catch (NoSuchAlgorithmException ex) { + LOG.error("generateX509Certificate: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider: " + + ex.getMessage()); + throw new CryptoException(ex); + } catch (Exception ex) { + LOG.error("generateX509Certificate: unable to generate X509 Certificate: " + ex.getMessage()); + throw new CryptoException("Unable to generate X509 Certificate"); + } + + return cert; + } + + public static boolean validatePKCS7Signature(String data, String signature, PublicKey publicKey) { + + try { + SignerInformationStore signerStore = null; + try (InputStream sigIs = new ByteArrayInputStream(Base64.decode(signature.getBytes(StandardCharsets.UTF_8)))) { + CMSProcessable content = new CMSProcessableByteArray(data.getBytes(StandardCharsets.UTF_8)); + CMSSignedData signedData = new CMSSignedData(content, sigIs); + signerStore = signedData.getSignerInfos(); + } + + Collection signers = signerStore.getSigners(); + Iterator it = signers.iterator(); + + SignerInformationVerifier infoVerifier = new JcaSimpleSignerInfoVerifierBuilder() + .setProvider(BC_PROVIDER).build(publicKey); + while (it.hasNext()) { + SignerInformation signerInfo = (SignerInformation) it.next(); + if (signerInfo.verify(infoVerifier)) { + return true; + } + } + } catch (CMSException ex) { + LOG.error("validatePKCS7Signature: unable to initialize CMSSignedData object: " + ex.getMessage()); + throw new CryptoException(ex); + } catch (OperatorCreationException ex) { + LOG.error("validatePKCS7Signature: Caught OperatorCreationException when creating JcaSimpleSignerInfoVerifierBuilder: " + + ex.getMessage()); + throw new CryptoException(ex); + } catch (IOException ex) { + LOG.error("validatePKCS7Signature: Caught IOException when closing InputStream: " + ex.getMessage()); + throw new CryptoException(ex); + } catch (Exception ex) { + LOG.error("validatePKCS7Signature: unable to validate signature: " + ex.getMessage()); + throw new CryptoException(ex.getMessage()); + } + + return false; + } + + public static String x509CertificateToPem(X509Certificate cert) { + StringWriter writer = new StringWriter(); + try { + try (JcaPEMWriter pemWriter = new JcaPEMWriter(writer)) { + pemWriter.writeObject(cert); + pemWriter.flush(); + pemWriter.close(); + } + } catch (IOException ex) { + LOG.error("x509CertificateToPem: unable to convert X509 cert to PEM: " + ex.getMessage()); + return null; + } + + return writer.toString(); + } + + public static void main(String [] args) throws CryptoException { + if (args.length >= 2) { + String op = args[0]; + if ("sign".equals(op)) { + if (args.length == 3) { + String sig = Crypto.sign(args[1], Crypto.loadPrivateKey(new File(args[2]))); + System.out.println(sig); + System.exit(0); + } + } else if ("verify".equals(op)) { + if (args.length == 4) { + if (Crypto.verify(args[1], Crypto.loadPublicKey(new File(args[2])), args[3])) { + System.out.println("Verified."); + } else { + System.out.println("NOT VERIFIED"); + } + System.exit(0); + } + } else if ("public".equals(op)) { + if (args.length == 2) { + String pub = encodedFile(new File(args[1])); + Crypto.loadPublicKey(ybase64DecodeString(pub)); //throws if something is wrong + System.out.println(pub); + System.exit(0); + } + } else if ("private".equals(op)) { + if (args.length == 2) { + try { + String priv = encodedFile(new File(args[1])); + Crypto.loadPrivateKey(ybase64DecodeString(priv)); //throws if something is wrong + System.out.println(priv); + System.exit(0); + } catch (Exception e) { + System.out.println("*** " + e.getMessage()); + System.exit(1); + } + } + } + } + System.out.println("usage: r Crypto private privateKeyFile"); + System.out.println("usage: r Crypto public publicKeyFile"); + System.out.println("usage: r Crypto sign msg privateKeyFile"); + System.out.println("usage: r Crypto verify msg privateKeyFile signature"); + System.exit(1); + } + +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/CryptoException.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/CryptoException.java new file mode 100644 index 00000000000..19ed051aa0e --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/CryptoException.java @@ -0,0 +1,86 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.util; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; + +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.pkcs.PKCSException; + +public class CryptoException extends RuntimeException { + + private static final long serialVersionUID = -4194687652165603898L; + + public CryptoException() { + super(); + } + + public CryptoException(String message) { + super(message); + } + + public CryptoException(NoSuchAlgorithmException e) { + super(e); + } + + public CryptoException(InvalidKeyException e) { + super(e); + } + + public CryptoException(NoSuchProviderException e) { + super(e); + } + + public CryptoException(SignatureException e) { + super(e); + } + + public CryptoException(FileNotFoundException e) { + super(e); + } + + public CryptoException(IOException e) { + super(e); + } + + public CryptoException(CertificateException e) { + super(e); + } + + public CryptoException(InvalidKeySpecException e) { + super(e); + } + + public CryptoException(OperatorCreationException e) { + super(e); + } + + public CryptoException(PKCSException e) { + super(e); + } + + public CryptoException(CMSException e) { + super(e); + } +} diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/Validate.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/Validate.java new file mode 100644 index 00000000000..d209c7d0921 --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/Validate.java @@ -0,0 +1,50 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A utility class to validate various types. + */ +public class Validate { + + private static final String PRINCIPAL_REGEX = "((([a-zA-Z_][a-zA-Z0-9_-]*\\.)*[a-zA-Z_][a-zA-Z0-9_-]*):)?(([a-zA-Z_][a-zA-Z0-9_-]*\\.)*[a-zA-Z_][a-zA-Z0-9_-]*)"; + private static final String DOMAIN_REGEX = "([a-zA-Z_][a-zA-Z0-9_-]*\\.)*[a-zA-Z_][a-zA-Z0-9_-]*"; + + private static Pattern principalPattern = Pattern.compile(PRINCIPAL_REGEX); + private static Pattern domainPattern = Pattern.compile(DOMAIN_REGEX); + + /** + * @param name a principal name to validate + * @return true if the principal name is valid, false otherwise. + */ + public static boolean principalName(String name) { + Matcher matcher = principalPattern.matcher(name); + return matcher.matches(); + } + + /** + * @param name a domain name to validate + * @return true if the domain name is valid, false otherwise. + */ + public static boolean domainName(String name) { + Matcher matcher = domainPattern.matcher(name); + return matcher.matches(); + } +} + diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/YBase64.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/YBase64.java new file mode 100644 index 00000000000..531442045a9 --- /dev/null +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/util/YBase64.java @@ -0,0 +1,305 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.util; + +import java.util.Arrays; + +/** + * Y64 Encode/Decode support: + * URL friendly base64 encoding, it replaces + and / with . and _ and uses - for padding. + * Original implementation is from Java Platforms + */ + +public class YBase64 { + + static final byte[] Y64_ARRAY = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '.', '_' + }; + + public static final byte[] Y64_DECODE_ARRAY = { + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xff, 62, (byte) 0xee, 52, + 53, 54, 55, 56, 57, 58, 59, 60, 61, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, 63, + (byte) 0xee, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee, + (byte) 0xee, (byte) 0xee, (byte) 0xee, (byte) 0xee + }; + + private static final byte decodeByte(int index) { + // java has signed bytes, and the good values are up to 122, so any + // negative indexes are signed byte conversions of >128 and invalid + if (index < 0 || index >= Y64_DECODE_ARRAY.length) { + return (byte) 0xee; + } + return Y64_DECODE_ARRAY[index]; + } + + private static final byte encode1(byte ba) { + // enc1: Y64_array[(a >> 2)]; + // have to make value into an int so we can do shifts. + // but we need to mask off the sign bit. + final int a = ((int) ba) & 0x00ff; + final int i = (a >> 2); + if (i < 0 || i >= Y64_DECODE_ARRAY.length) { + return (byte) 0xee; + } + return Y64_ARRAY[i]; + } + + private static final byte encode2(byte ba, byte bb) { + // enc2: Y64_array[((a << 4) & 0x30) + (b >> 4)]; + // have to make value into an int so we can do shifts. + // but we need to mask off the sign bit. + final int a = ((int) ba) & 0x00ff; + final int b = ((int) bb) & 0x00ff; + final int i = ((a << 4) & 0x30) + (b >> 4); + if (i < 0 || i >= Y64_DECODE_ARRAY.length) { + return (byte) 0xee; + } + + return Y64_ARRAY[i]; + } + + private static final byte encode3(byte bb, byte bc) { + // enc3: Y64_array[((b << 2) & 0x3C) + (c >> 6)]; + // have to make value into an int so we can do shifts. + // but we need to mask off the sign bit. + final int b = ((int) bb) & 0x00ff; + final int c = ((int) bc) & 0x00ff; + final int i = ((b << 2) & 0x3C) + (c >> 6); + if (i < 0 || i >= Y64_DECODE_ARRAY.length) { + return (byte) 0xee; + } + + return Y64_ARRAY[i]; + } + + private static final byte encode4(byte bc) { + // enc4: Y64_array[c & 0x3F]; + // have to make value into an int so we can do shifts. + // but we need to mask off the sign bit. + final int i = ((int) bc) & 0x003f; + if (i < 0 || i >= Y64_DECODE_ARRAY.length) { + return (byte) 0xee; + } + + return Y64_ARRAY[i]; + } + + private static final byte decode1(byte a, byte b) { + return (byte) ((a << 2) + (b >> 4)); + } + + private static final byte decode2(byte b, byte c) { + return (byte) ((b << 4) + (c >> 2)); + } + + private static final byte decode3(byte c, byte d) { + return (byte) ((c << 6) + d); + } + + /** + * Decode the given byte array and return the result + * @param inBytes byte array to be decoded + * @return decoded byte array + * @throws CryptoException in case of invalid padding or characters + */ + public static byte[] decode(byte[] inBytes) { + + if (null == inBytes) { + throw new NullPointerException("Null input buffer"); + } + + /* Sanity check, should always be padded at the end. */ + int len = inBytes.length; + if (len % 4 != 0 && inBytes[len - 1] == '\0') { + len -= 1; + } + + if (len % 4 != 0) { + throw new CryptoException("String not padded ie, input string not modulo 4 len = " + len + + " len%4= " + len % 4); + } + + byte[] out = new byte[(int) y64decodeLen(len)]; + + int i = 0; + int j = 0; + int tlen = 0; + while (i < len) { + tlen = (len - i); + if (tlen > 4) { + tlen = 4; + } + + /* Figure out how long "tlen" really is */ + if (inBytes[i + 3] == '-') { + tlen--; + } + if (inBytes[i + 2] == '-') { + tlen--; + } + + if (inBytes[i + 1] == '-') { /* This case should NEVER happen. */ + throw new CryptoException("Too Many pad characters ( this should never happen )"); + } + + /* decode */ + byte a = decodeByte(inBytes[i++]); + byte b = decodeByte(inBytes[i++]); + byte c = decodeByte(inBytes[i++]); + byte d = decodeByte(inBytes[i++]); + + /* validate */ + if (a == (byte) 0xee || b == (byte) 0xee || c == (byte) 0xee || d == (byte) 0xee) { + throw new CryptoException("Unrecognized characters in y64-encoded input starting at: " + (i - 4)); + } + + if (tlen == 4) { + // dec1: ((a << 2) + (b >> 4)); + out[j++] = decode1(a, b); + // dec2: ((b << 4) + (c >> 2)); + out[j++] = decode2(b, c); + // dec3: ((c << 6) + d); + out[j++] = decode3(c, d); + } else if (tlen == 3) { + if ((c & (byte) 0x03) != 0) { + throw new CryptoException("Unknown decode error c & 0x03 failed, c-pos: " + (i - 2)); + } + // dec1: ((a << 2) + (b >> 4)); + out[j++] = decode1(a, b); + // dec2: ((b << 4) + (c >> 2)); + out[j++] = decode2(b, c); + } else { /* tlen == 2 */ + if ((b & (byte) 0x0F) != 0) { + throw new CryptoException("Invalid decode. b & 0x0f failed, b-pos: " + (i - 3)); + } + // dec1: ((a << 2) + (b >> 4)); + out[j++] = decode1(a, b); + } + } + + return Arrays.copyOf(out, j); + } + + /** + * Encode given byte array into Y64 format. + * @param inBytes data to be encoded + * @return encoded Y64 byte array + * @throws NullPointerException if the input buffer is null + */ + public static byte[] encode(byte[] inBytes) { + + if (null == inBytes) { + throw new NullPointerException("input buffer was null"); + } + + /* Sanity check, should always be padded at the end. */ + if (inBytes.length < 1) { + return new byte[] {}; + } + + int len = inBytes.length; + int encodeLen = y64encodeLen(len); + byte[] out = new byte[encodeLen]; + int j = 0; + int tlen = 0; + + for (int i = 0; i < len; i += 3) { + + tlen = (len - i); + if (tlen > 3) { + tlen = 3; + } + + byte a; + byte b; + byte c; + + if (tlen == 1) { + a = inBytes[i]; + b = 0; + c = 0; + // enc1: Y64_array[(a >> 2)]; + out[j++] = encode1(a); + // enc2: Y64_array[((a << 4) & 0x30) + (b >> 4)]; + out[j++] = encode2(a, b); + out[j++] = '-'; + out[j++] = '-'; + } else if (tlen == 2) { + a = inBytes[i]; + b = inBytes[i + 1]; + c = 0; + // enc1: Y64_array[(a >> 2)]; + out[j++] = encode1(a); + // enc2: Y64_array[((a << 4) & 0x30) + (b >> 4)]; + out[j++] = encode2(a, b); + // enc3: Y64_array[((b << 2) & 0x3C) + (c >> 6)]; + out[j++] = encode3(b, c); + out[j++] = '-'; + } else { + a = inBytes[i]; + b = inBytes[i + 1]; + c = inBytes[i + 2]; + // enc1: Y64_array[(a >> 2)]; + out[j++] = encode1(a); + // enc2: Y64_array[((a << 4) & 0x30) + (b >> 4)]; + out[j++] = encode2(a, b); + // enc3: Y64_array[((b << 2) & 0x3C) + (c >> 6)]; + out[j++] = encode3(b, c); + // enc4: Y64_array[c & 0x3F]; + out[j++] = encode4(c); + } + } + + return Arrays.copyOf(out, j); + } + + static int y64decodeLen(int len) { + // this is not -1 because strings have no null terminator. + return (((len + 3) / 4) * 3) + 1; + } + + static int y64encodeLen(int len) { + // Could check for response length overflow - generated response + // is greater than max size_t can represent. + return ((len + 2) / 3 * 4) + 1; + } +} + diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/CertificateAuthorityTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/CertificateAuthorityTest.java new file mode 100644 index 00000000000..3bee485601d --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/CertificateAuthorityTest.java @@ -0,0 +1,109 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import static org.testng.Assert.*; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.Authority.CredSource; +import com.yahoo.athenz.auth.impl.CertificateAuthority; + +public class CertificateAuthorityTest { + + @Test + public void testGetDomain() { + CertificateAuthority authority = new CertificateAuthority(); + authority.initialize(); + assertNull(authority.getDomain()); + } + + @Test + public void testGetHeader() { + CertificateAuthority authority = new CertificateAuthority(); + authority.initialize(); + assertNull(authority.getHeader()); + } + + @Test + public void testGetCredSource() { + CertificateAuthority authority = new CertificateAuthority(); + authority.initialize(); + assertEquals(CredSource.CERTIFICATE, authority.getCredSource()); + } + + @Test + public void testHeaderAuthenticate() { + + CertificateAuthority authority = new CertificateAuthority(); + authority.initialize(); + assertNull(authority.authenticate("v=U1;d=domain;n=service;s=sig", null, "GET", null)); + } + + @Test + public void testExtractX509CertCN() throws Exception, IOException { + CertificateAuthority authority = new CertificateAuthority(); + authority.initialize(); + + try (InputStream inStream = new FileInputStream("src/test/resources/valid_cn_x509.cert")) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream); + + String cn = authority.extractX509CertCN(cert); + assertEquals("athenz.syncer", cn); + } + } + + @Test + public void testAuthenticateCertificate() throws Exception, IOException { + CertificateAuthority authority = new CertificateAuthority(); + authority.initialize(); + + try (InputStream inStream = new FileInputStream("src/test/resources/valid_cn_x509.cert")) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream); + + X509Certificate[] certs = new X509Certificate[1]; + certs[0] = cert; + Principal principal = authority.authenticate(certs, null); + assertNotNull(principal); + assertEquals("athenz", principal.getDomain()); + assertEquals("syncer", principal.getName()); + } + } + + @Test + public void testAuthenciateInvalidArray() { + + CertificateAuthority authority = new CertificateAuthority(); + authority.initialize(); + StringBuilder errMsg = new StringBuilder(512); + Principal principal = authority.authenticate(null, errMsg); + assertNull(principal); + + X509Certificate[] certs = new X509Certificate[1]; + certs[0] = null; + principal = authority.authenticate(certs, errMsg); + assertNull(principal); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/KerberosAuthorityTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/KerberosAuthorityTest.java new file mode 100644 index 00000000000..d13298bcbf5 --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/KerberosAuthorityTest.java @@ -0,0 +1,382 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.kerberos.KerberosPrincipal; +import javax.security.auth.kerberos.KerberosTicket; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.KerberosAuthority; +import com.yahoo.athenz.auth.token.KerberosToken; + +import static org.testng.Assert.*; +import java.lang.reflect.Field; + +public class KerberosAuthorityTest { + + final static String KRB_LOGIN_CB_CLASS = "com.yahoo.athenz.auth.impl.TestLoginCallbackHandler"; + + @BeforeTest + private void beforeTest() { + + } + + @Test(groups="kerberos-tests") + public void testLoginConfig() { + + String keyTabConfFile = null; + String servicePrincipal = null; + KerberosAuthority.LoginConfig loginConfig = new KerberosAuthority.LoginConfig(keyTabConfFile, servicePrincipal); + assertEquals(loginConfig.isDebugEnabled(), false); + AppConfigurationEntry[] conf = loginConfig.getAppConfigurationEntry(null); + AppConfigurationEntry entry = conf[0]; + java.util.Map options = entry.getOptions(); + assertNull(options.get("principal")); + assertEquals(options.get("useKeyTab"), "false"); + assertEquals(options.get("useTicketCache"), "true"); + assertEquals(options.get("renewTGT"), "true"); + assertNull(options.get("debug")); + assertNull(options.get("ticketCache")); + + // set properties and remake the login config + System.setProperty(KerberosAuthority.KRB_PROP_LOGIN_RENEW_TGT, "false"); + System.setProperty(KerberosAuthority.KRB_PROP_LOGIN_USE_TKT_CACHE, "false"); + System.setProperty(KerberosAuthority.KRB_PROP_LOGIN_TKT_CACHE_NAME, "/tmp/cache"); + System.setProperty(KerberosAuthority.KRB_PROP_DEBUG, "TRUE"); + keyTabConfFile = "my.keytab"; + servicePrincipal = "juke"; + loginConfig = new KerberosAuthority.LoginConfig(keyTabConfFile, servicePrincipal); + assertEquals(loginConfig.isDebugEnabled(), true); + conf = loginConfig.getAppConfigurationEntry(null); + entry = conf[0]; + options = entry.getOptions(); + assertEquals(options.get("principal"), "juke"); + assertEquals(options.get("useKeyTab"), "true"); + assertEquals(options.get("useTicketCache"), "false"); + assertEquals(options.get("renewTGT"), "false"); + assertEquals(options.get("debug"), "true"); + assertNull(options.get("ticketCache")); + assertEquals(options.get("keyTab"), "my.keytab"); + System.clearProperty(KerberosAuthority.KRB_PROP_LOGIN_RENEW_TGT); + System.clearProperty(KerberosAuthority.KRB_PROP_LOGIN_USE_TKT_CACHE); + System.clearProperty(KerberosAuthority.KRB_PROP_LOGIN_TKT_CACHE_NAME); + System.clearProperty(KerberosAuthority.KRB_PROP_DEBUG); + } + + @Test(groups="kerberos-tests") + public void testKerberosAuthorityBadCreds() { + + KerberosAuthority authority = new KerberosAuthority("myserver", "src/test/resources/example.keytab", null); + authority.initialize(); + + assertNull(authority.getDomain()); + assertEquals(authority.getHeader(), KerberosAuthority.KRB_AUTH_HEADER); + + KerberosToken token = null; + String creds = "invalid_creds"; + String remoteAddr = "some.address"; + try { + token = new KerberosToken(creds, remoteAddr); + fail("new KerberosToken with bad creds"); + } catch (Exception exc) { + String msg = exc.getMessage(); + assertTrue(msg.contains("creds do not contain required Negotiate component")); + } + + creds = KerberosToken.KRB_AUTH_VAL_FLD + " YIGeBgYrBgEFBQKggZMwgZCgGjAYBgorBgEEAYI3AgIeBgorBgEEAYI3AgIKonIEcE5FR09FWFRTAAfakecreds"; + token = new KerberosToken(creds, remoteAddr); + + StringBuilder errMsg = new StringBuilder(); + Principal principal = authority.authenticate(token.getSignedToken(), null, "GET", errMsg); + assertNull(principal); + } + + @Test(groups="kerberos-tests") + public void testKerberosAuthorityMockPrivExcAction() throws Exception { + + System.setProperty(KerberosToken.KRB_PROP_TOKEN_PRIV_ACTION, "com.yahoo.athenz.auth.impl.MockPrivExcAction"); + System.setProperty(KerberosToken.KRB_PROP_TOKEN_PRIV_ACTION + "_TEST_REALM", "USER_REALM"); + String token = "YIGeBgYrBgEFBQKggZMwgZCgGjAYBgorBgEEAYI3AgIeBgorBgEEAYI3AgIKonIEcE5FR09FWFRTAAfakecreds"; + System.setProperty(KerberosAuthority.KRB_PROP_SVCPRPL, "myserver"); + System.setProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS, KRB_LOGIN_CB_CLASS); + System.setProperty(KerberosAuthority.KRB_PROP_KEYTAB, "src/test/resources/example.keytab"); + + KerberosAuthority authority = new KerberosAuthority(); + authority.initialize(); + + String creds = KerberosToken.KRB_AUTH_VAL_FLD + " " + token; + String remoteAddr = "localhost"; + KerberosToken ktoken = new KerberosToken(creds, remoteAddr); + boolean ret = ktoken.validate(null, null); + assertEquals(ret, true); + + StringBuilder errMsg = new StringBuilder(); + Principal principal = authority.authenticate(ktoken.getSignedToken(), null, "GET", errMsg); + assertNotNull(principal); + assertNotNull(principal.getAuthority()); + assertEquals(principal.getCredentials(), ktoken.getSignedToken()); + assertEquals(principal.getDomain(), ktoken.getDomain()); + assertEquals(principal.getDomain(), KerberosToken.USER_DOMAIN); + assertEquals(principal.getName(), ktoken.getUserName()); + assertTrue(principal.getName().indexOf('@') == -1); + + principal = authority.authenticate(ktoken.getSignedToken(), null, "GET", null); + assertNotNull(principal); + System.out.println("test mock priv action: got principal=" + principal ); + + // test with ygrid realm + System.setProperty(KerberosToken.KRB_PROP_TOKEN_PRIV_ACTION + "_TEST_REALM", KerberosToken.KRB_USER_REALM); + ktoken = new KerberosToken(creds, remoteAddr); + ret = ktoken.validate(null, null); + assertEquals(ret, true); + + errMsg = new StringBuilder(); + principal = authority.authenticate(ktoken.getSignedToken(), null, "GET", errMsg); + assertNotNull(principal); + assertNotNull(principal.getAuthority()); + assertEquals(principal.getCredentials(), ktoken.getSignedToken()); + assertEquals(principal.getDomain(), ktoken.getDomain()); + assertEquals(principal.getDomain(), KerberosToken.KRB_USER_DOMAIN); + assertEquals(principal.getName(), ktoken.getUserName()); + assertTrue(principal.getName().indexOf('@') == -1); + + principal = authority.authenticate(ktoken.getSignedToken(), null, "GET", null); + assertNotNull(principal); + System.out.println("test mock priv action: got principal=" + principal ); + + // test with invalid realm + System.setProperty(KerberosToken.KRB_PROP_TOKEN_PRIV_ACTION + "_TEST_REALM", "REALM.SOMECOMPANY.COM"); + ktoken = new KerberosToken(creds, remoteAddr); + ret = ktoken.validate(null, null); + assertEquals(ret, false); + + errMsg = new StringBuilder(); + principal = authority.authenticate(ktoken.getSignedToken(), null, "GET", errMsg); + assertNull(principal); + + principal = authority.authenticate(ktoken.getSignedToken(), null, "GET", null); + assertNull(principal); + System.out.println("test mock priv action: got principal=" + principal ); + + principal = authority.authenticate(null, null, "GET", null); + assertNull(principal); + + System.clearProperty(KerberosToken.KRB_PROP_TOKEN_PRIV_ACTION); + System.clearProperty(KerberosAuthority.KRB_PROP_SVCPRPL); + System.clearProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS); + System.clearProperty(KerberosAuthority.KRB_PROP_KEYTAB); + } + + @Test(groups="kerberos-tests") + public void testKerberosAuthorityJaas() { + + System.setProperty("java.security.auth.login.config", "src/test/resources/jaas.conf"); + System.setProperty("java.security.krb5.kdc", "localhost"); + System.setProperty( "sun.security.krb5.debug", "true"); + System.setProperty( "javax.security.auth.useSubjectCredsOnly", "true"); + System.setProperty(KerberosAuthority.KRB_PROP_JAASCFG, "Server"); + System.setProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS, KRB_LOGIN_CB_CLASS); + + KerberosAuthority kauth = new KerberosAuthority(); + kauth.initialize(); + Exception initState = kauth.getInitState(); + assertNotNull(initState); + assertTrue(initState instanceof javax.security.auth.login.LoginException); + + System.clearProperty("java.security.auth.login.config"); + System.clearProperty("java.security.krb5.kdc"); + System.clearProperty("sun.security.krb5.debug"); + System.clearProperty("javax.security.auth.useSubjectCredsOnly"); + System.clearProperty(KerberosAuthority.KRB_PROP_JAASCFG); + System.clearProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS); + } + + @Test(groups="kerberos-tests") + public void testKerberosAuthorityKeytab() { + + System.setProperty(KerberosAuthority.KRB_PROP_KEYTAB, "src/test/resources/example.keytab"); + System.setProperty(KerberosAuthority.KRB_PROP_SVCPRPL, "myserver"); + System.setProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS, KRB_LOGIN_CB_CLASS); + System.setProperty( "sun.security.krb5.debug", "true"); + System.setProperty( "java.security.krb5.realm", "EXAMPLE.COM"); + System.setProperty( "java.security.krb5.kdc", "localhost"); + + KerberosAuthority kauth = new KerberosAuthority(); + kauth.initialize(); + Exception initState = kauth.getInitState(); + assertNull(initState); + + System.clearProperty(KerberosAuthority.KRB_PROP_KEYTAB); + System.clearProperty(KerberosAuthority.KRB_PROP_SVCPRPL); + System.clearProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS); + System.clearProperty( "java.security.krb5.realm"); + System.clearProperty( "java.security.krb5.kdc"); + } + + @Test(groups="kerberos-tests") + public void testKerberosAuthorityIsOurPrincipal() { + System.setProperty(KerberosAuthority.KRB_PROP_KEYTAB, "src/test/resources/example.keytab"); + System.setProperty(KerberosAuthority.KRB_PROP_SVCPRPL, "myserver@EXAMPLE.COM"); + System.setProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS, KRB_LOGIN_CB_CLASS); + System.setProperty( "sun.security.krb5.debug", "true"); + + KerberosAuthority kauth = new KerberosAuthority(); + kauth.initialize(); + Exception initState = kauth.getInitState(); + assertNull(initState); + + KerberosPrincipal princ = new KerberosPrincipal("myserver@EXAMPLE.COM"); + String token = "YIIB6wYJKoZIhvcSAQICAQBuggHaMIIB1qADAgEFoQMCAQ6iBwMFAAAAAACjggEGYYIBAjCB/6ADAgEFoQ0bC0VYQU1QTEUuQ09NojAwLqADAgEAoScwJRsHZGF0YWh1YhsTZGF0YWh1Yi5leGFtcGxlLmNvbRsFYnVsbHmjgbYwgbOgAwIBA6KBqwSBqMJGf4H5nrRIdDyNxHp5fwxW6lsiFi+qUjryPvgiOAl/XldfwmKd9wXbQn00VBNhK+oVxmKv0V0J80e4oTdUnc+NlU/BJNCfsLPFTdYntc4A/ffdnsY7/U5HktTaWMfhvWxYocvhqISFTIFUT1+pH5742IWYNTgvFd5vkudibB3ijCanbMYv9CQXEjV+380rnf3gdLD2JGuxmaU78aJjDDKETL6Ck/qz8KSBtjCBs6ADAgEDooGrBIGoMrzLCTUi59wEoWX02+42K5m1MzW6HMNSuvfQeVGJdzPBsiFmZweNfJF6L9LdmLjQR4jSVUhVo3neFZmUN8G532wvZeKbHOtkXTnLRRdif+DoKyI8GOkbHu1CZlevcQZ0sgzyiH0wfQ/0nguE4kH7a2bM7HlV7N6MRGkC4DDkJZDNHxQr27FbZqrqEyw498HXPTtF93JGsKjXB8Z/wDaPs4PpdfoThTol"; + byte[] asn1Encoding = token.getBytes(); + byte[] sessionKey = "xyz".getBytes(); + long endMillis = System.currentTimeMillis() + 2000; + java.util.Date endDate = new java.util.Date(); + endDate.setTime(endMillis); + KerberosTicket ticket = new KerberosTicket(asn1Encoding, princ, princ, sessionKey, 0, null, null, null, endDate, null, null); + + boolean ours = kauth.isTargetPrincipal(ticket, "myserver@EXAMPLE.COM"); + assertTrue(ours); + + KerberosPrincipal clientPrinc = new KerberosPrincipal("myclient@EXAMPLE.COM"); + ticket = new KerberosTicket(asn1Encoding, princ, clientPrinc, sessionKey, 0, null, null, null, endDate, null, null); + + ours = kauth.isTargetPrincipal(ticket, "myservice@EXAPLE.COM"); + assertFalse(ours); + + System.clearProperty(KerberosAuthority.KRB_PROP_SVCPRPL); + System.clearProperty(KerberosAuthority.KRB_PROP_KEYTAB); + System.clearProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS); + } + + @Test(groups="kerberos-tests") + public void testKerberosAuthorityLogin() { + + System.setProperty(KerberosAuthority.KRB_PROP_LOGIN_WINDOW, "1000"); + System.setProperty(KerberosAuthority.KRB_PROP_KEYTAB, "src/test/resources/example.keytab"); + System.setProperty(KerberosAuthority.KRB_PROP_SVCPRPL, "myserver@EXAMPLE.COM"); + System.setProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS, KRB_LOGIN_CB_CLASS); + System.setProperty( "sun.security.krb5.debug", "true"); + + KerberosAuthority kauth = new KerberosAuthority(); + kauth.initialize(); + Exception initState = kauth.getInitState(); + assertNull(initState); + + kauth.login(false); + initState = kauth.getInitState(); + assertNull(initState); + + try { + Thread.sleep(2000); + } catch (Exception exc) { + System.out.println("testKerberosAuthorityLogin: sleep failed: continuing..."); + } + + kauth.login(true); + initState = kauth.getInitState(); + assertNull(initState); + + System.clearProperty(KerberosAuthority.KRB_PROP_LOGIN_WINDOW); + System.clearProperty(KerberosAuthority.KRB_PROP_SVCPRPL); + System.clearProperty(KerberosAuthority.KRB_PROP_KEYTAB); + System.clearProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS); + } + + @Test(groups="kerberos-tests") + public void testKerberosAuthorityRefreshLogin() { + System.setProperty(KerberosAuthority.KRB_PROP_LOGIN_WINDOW, "1000"); + System.setProperty(KerberosAuthority.KRB_PROP_KEYTAB, "src/test/resources/example.keytab"); + System.setProperty(KerberosAuthority.KRB_PROP_SVCPRPL, "myserver@EXAMPLE.COM"); + System.setProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS, KRB_LOGIN_CB_CLASS); + System.setProperty("sun.security.krb5.debug", "true"); + + KerberosAuthority kauth = new KerberosAuthority(); + kauth.initialize(); + Exception initState = kauth.getInitState(); + assertNull(initState); + + long lastLogin = kauth.getLastLogin(); + long now = System.currentTimeMillis(); + assertTrue(lastLogin <= now); + + long loginWindow = kauth.getLoginWindow(); + assertEquals(loginWindow, 1000); + + boolean refreshed = kauth.refreshLogin("myserver@EXAMPLE.COM"); + assertEquals(refreshed, true); + initState = kauth.getInitState(); + assertNull(initState); + + try { + Thread.sleep(2000); + } catch (Exception exc) { + System.out.println("testKerberosAuthorityLogin: sleep failed: continuing..."); + } + + refreshed = kauth.refreshLogin("myserver@EXAMPLE.COM"); + assertEquals(refreshed, true); + initState = kauth.getInitState(); + assertNull(initState); + + System.clearProperty(KerberosAuthority.KRB_PROP_LOGIN_WINDOW); + System.clearProperty(KerberosAuthority.KRB_PROP_SVCPRPL); + System.clearProperty(KerberosAuthority.KRB_PROP_KEYTAB); + System.clearProperty(KerberosAuthority.KRB_PROP_LOGIN_CB_CLASS); + } + + @Test(groups="kerberos-tests") + public void testSetInitState() throws NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException { + Class c = KerberosAuthority.class; + KerberosAuthority check = new KerberosAuthority(); + + Exception e = null; + + check.setInitState(e); + + Field f = c.getDeclaredField("initState"); + f.setAccessible(true); + Exception m = (Exception) f.get(check); + + assertNull(m); + } + + @Test(groups="kerberos-tests") + public void testSetLoginWindow() throws NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException { + Class c = KerberosAuthority.class; + KerberosAuthority check = new KerberosAuthority(); + + check.setLoginWindow((long)100); + + Field f = c.getDeclaredField("loginWindow"); + f.setAccessible(true); + long m = (long) f.get(check); + + assertEquals(m,100); + } + + @Test(groups="kerberos-tests") + public void testIsTargetPrincipalIlligal() { + KerberosAuthority check = new KerberosAuthority(); + + assertFalse(check.isTargetPrincipal(null,null)); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/KeyStoreMock.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/KeyStoreMock.java new file mode 100644 index 00000000000..cb2dee51b61 --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/KeyStoreMock.java @@ -0,0 +1,88 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import com.yahoo.athenz.auth.KeyStore; +import com.yahoo.athenz.auth.impl.RoleAuthority; + +public class KeyStoreMock implements KeyStore { + + String servicePublicKeyStringK0; + String servicePublicKeyStringK1; + String ztsPublicKeyStringK0; + String ztsPublicKeyStringK1; + String hostPublic = null; + + public KeyStoreMock() throws IOException { + Path path = Paths.get("./src/test/resources/fantasy_public_k0.key"); + servicePublicKeyStringK0 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/fantasy_public_k1.key"); + servicePublicKeyStringK1 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/zts_public_k0.key"); + ztsPublicKeyStringK0 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/zts_public_k1.key"); + ztsPublicKeyStringK1 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/host_public.key"); + hostPublic = new String(Files.readAllBytes(path)); + } + + @Override + public String getPublicKey(String domain, String service, String keyId) { + + // special case for host certs - no domain and service + + if (domain == null && service == null) { + return hostPublic; + } + + // handle rest of the cases for other authorities + + if ("sports".equals(domain) && "fantasy".equals(service) && "0".equals(keyId)) { + return servicePublicKeyStringK0; + } else if ("sports".equals(domain) && "fantasy".equals(service) && "1".equals(keyId)) { + return servicePublicKeyStringK1; + } else if ("sports".equals(domain) && "nfl".equals(service) && "0".equals(keyId)) { + return servicePublicKeyStringK0; + } else if ("sports".equals(domain) && "nfl".equals(service) && "1".equals(keyId)) { + return servicePublicKeyStringK1; + } else if ("cd.project".equals(domain) && "authority".equals(service) && "0".equals(keyId)) { + return servicePublicKeyStringK0; + } else if ("cd.step".equals(domain) && "authority".equals(service) && "0".equals(keyId)) { + return servicePublicKeyStringK0; + } else if (RoleAuthority.SYS_AUTH_DOMAIN.equals(domain) + && RoleAuthority.ZTS_SERVICE_NAME.equals(service) + && "0".equals(keyId)) { + return ztsPublicKeyStringK0; + } else if (RoleAuthority.SYS_AUTH_DOMAIN.equals(domain) + && RoleAuthority.ZTS_SERVICE_NAME.equals(service) + && "1".equals(keyId)) { + return ztsPublicKeyStringK1; + } else if ("sys.auth".equals(domain) && "zms".equals(service) && "0".equals(keyId)) { + return servicePublicKeyStringK0; + } + + return null; + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/MockPrivExcAction.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/MockPrivExcAction.java new file mode 100644 index 00000000000..bc033c1ab7d --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/MockPrivExcAction.java @@ -0,0 +1,40 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import java.security.PrivilegedExceptionAction; + +import com.yahoo.athenz.auth.token.KerberosToken; + +public class MockPrivExcAction implements PrivilegedExceptionAction { + + byte[] kerberosTicket; + String realm = System.getProperty(KerberosToken.KRB_PROP_TOKEN_PRIV_ACTION + "_TEST_REALM", KerberosToken.KRB_USER_REALM); + + public MockPrivExcAction(String kerberosTicket) { + this(kerberosTicket.getBytes()); + } + public MockPrivExcAction(byte[] kerberosTicket) { + this.kerberosTicket = kerberosTicket; + } + + @Override + public String run() throws Exception { + String user = "myclient" + "@" + realm; + System.out.println("MockPrivExcAction run: return=" + user); + return user; + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/PrincipalAuthorityTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/PrincipalAuthorityTest.java new file mode 100644 index 00000000000..ef046da8886 --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/PrincipalAuthorityTest.java @@ -0,0 +1,598 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import java.lang.reflect.Field; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import static org.testng.Assert.*; + +import org.mockito.Mockito; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.KeyStore; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.Authority.CredSource; +import com.yahoo.athenz.auth.impl.PrincipalAuthority; +import com.yahoo.athenz.auth.impl.PrincipalAuthority.IpCheckMode; +import com.yahoo.athenz.auth.token.PrincipalToken; +import com.yahoo.athenz.auth.util.CryptoException; + +public class PrincipalAuthorityTest { + + private final String svcVersion = "S1"; + private final String svcDomain = "sports"; + private final String svcName = "fantasy"; + private final String host = "somehost.somecompany.com"; + private final String salt = "saltvalue"; + private final String testKeyVersionK0 = "0"; + private final String testKeyVersionK1 = "1"; + private final String usrVersion = "U1"; + private final String usrDomain = "user"; + private final String usrName = "john"; + + private final long expirationTime = 10; // 10 seconds + private String servicePrivateKeyStringK0 = null; + private String servicePrivateKeyStringK1 = null; + + @BeforeTest + private void loadKeys() throws IOException { + + Path path = Paths.get("./src/test/resources/fantasy_private_k0.key"); + servicePrivateKeyStringK0 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/fantasy_private_k1.key"); + servicePrivateKeyStringK1 = new String(Files.readAllBytes(path)); + } + + private String tamperWithServiceToken(String signedToken) { + String name = null; + String version = null; + String salt = null; + String domain = null; + String host = null; + String signature = null; + long timestamp = 0; + long expiryTime = 0; + + for (String item : signedToken.split(";")) { + String[] kv = item.split("="); + if (kv.length == 2) { + if ("n".equals(kv[0])) { + name = kv[1]; + } else if ("v".equals(kv[0])) { + version = kv[1]; + } else if ("a".equals(kv[0])) { + salt = kv[1]; + } else if ("d".equals(kv[0])) { + domain = kv[1]; + } else if ("h".equals(kv[0])) { + host = kv[1]; + } else if ("s".equals(kv[0])) { + signature = kv[1]; + } else if ("t".equals(kv[0])) { + timestamp = Long.parseLong(kv[1]); + } else if ("e".equals(kv[0])) { + expiryTime = Long.parseLong(kv[1]); + } + } + } + + name = "nfl"; // tamper here by changing the name + + String tamperedToken = new String("v=" + version + ";d=" + domain + + ";n=" + name + ";h=" + host + ";a=" + salt + ";t=" + + Long.toString(timestamp) + ";e=" + Long.toString(expiryTime) + + ";s=" + signature); + + return tamperedToken; + } + + @Test + public void testPrincipalAuthority() throws IOException, CryptoException { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = new KeyStoreMock(); + serviceAuthority.setKeyStore(keyStore); + + assertNull(serviceAuthority.getDomain()); + assertEquals(serviceAuthority.getHeader(), "Athenz-Principal-Auth"); + + // Create and sign token with no key version + PrincipalToken serviceToken = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .host(host).salt(salt).expirationWindow(expirationTime).build(); + serviceToken.sign(servicePrivateKeyStringK0); + + StringBuilder errMsg = new StringBuilder(); + Principal principal = serviceAuthority.authenticate( + serviceToken.getSignedToken(), null, "GET", errMsg); + + assertNotNull(principal); + assertNotNull(principal.getAuthority()); + assertEquals(principal.getCredentials(), serviceToken.getSignedToken()); + assertEquals(principal.getDomain(), serviceToken.getDomain()); + assertEquals(principal.getName(), serviceToken.getName()); + + principal = serviceAuthority.authenticate( + serviceToken.getSignedToken(), null, "GET", null); + assertNotNull(principal); + + // Create and sign token with key version 0 + serviceToken = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .host(host).salt(salt).expirationWindow(expirationTime) + .keyId(testKeyVersionK0).build(); + serviceToken.sign(servicePrivateKeyStringK0); + + principal = serviceAuthority.authenticate(serviceToken.getSignedToken(), null, "GET", errMsg); + + assertNotNull(principal); + assertEquals(principal.getCredentials(), serviceToken.getSignedToken()); + + // Create and sign token with key version 1 + serviceToken = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .host(host).salt(salt).expirationWindow(expirationTime).keyId(testKeyVersionK1).build(); + serviceToken.sign(servicePrivateKeyStringK1); + + principal = serviceAuthority.authenticate(serviceToken.getSignedToken(), null, "GET", errMsg); + + assertNotNull(principal); + assertEquals(principal.getCredentials(), serviceToken.getSignedToken()); + } + + @Test + public void testPrincipalAuthority_TamperedToken() throws IOException, + CryptoException { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = new KeyStoreMock(); + serviceAuthority.setKeyStore(keyStore); + + // Create and sign token + PrincipalToken serviceToken = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .host(host).salt(salt).expirationWindow(expirationTime).build(); + serviceToken.sign(servicePrivateKeyStringK0); + + String tokenToTamper = serviceToken.getSignedToken(); + + StringBuilder errMsg = new StringBuilder(); + Principal principal = serviceAuthority.authenticate( + tamperWithServiceToken(tokenToTamper), null, "GET", errMsg); + + // Service Authority should return null when authenticate() fails + assertNull(principal); + assertTrue(!errMsg.toString().isEmpty()); + assertTrue(errMsg.toString().contains("authenticate")); + + principal = serviceAuthority.authenticate( + tamperWithServiceToken(tokenToTamper), null, "GET", null); + assertNull(principal); + } + + @Test + public void testPrincipalAuthorityWithAuthorizedService() throws IOException, CryptoException { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = new KeyStoreMock(); + serviceAuthority.setKeyStore(keyStore); + + // Create and sign token with key version 0 + + List authorizedServices = new ArrayList<>(); + authorizedServices.add("sports.fantasy"); + authorizedServices.add("sports.hockey"); + + long issueTime = System.currentTimeMillis() / 1000; + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).ip("127.0.0.2").issueTime(issueTime).expirationWindow(expirationTime) + .authorizedServices(authorizedServices).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // now let's sign the token for an authorized service + + userTokenToSign.signForAuthorizedService("sports.fantasy", "1", servicePrivateKeyStringK1); + + // we're going to pass a different IP so we get the authorized service checks + + StringBuilder errMsg = new StringBuilder(); + Principal principal = serviceAuthority.authenticate(userTokenToSign.getSignedToken(), + "127.0.0.3", "POST", errMsg); + + assertNotNull(principal); + assertEquals(principal.getAuthorizedService(), "sports.fantasy"); + } + + @Test + public void testValidateAuthorizedServiceSingle() throws IOException { + + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = new KeyStoreMock(); + serviceAuthority.setKeyStore(keyStore); + + long issueTime = System.currentTimeMillis() / 1000; + // Create and sign token + List authorizedServices = new ArrayList<>(); + authorizedServices.add("sports.fantasy"); + + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime) + .authorizedServices(authorizedServices).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // now let's sign the token for an authorized service + + userTokenToSign.signForAuthorizedService("sports.fantasy", "1", servicePrivateKeyStringK1); + + // Create a token for validation using the signed data + assertEquals(serviceAuthority.validateAuthorizeService(userTokenToSign, null), "sports.fantasy"); + } + + @Test + public void testValidateAuthorizedServiceMultiple() throws IOException { + + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = new KeyStoreMock(); + serviceAuthority.setKeyStore(keyStore); + + long issueTime = System.currentTimeMillis() / 1000; + // Create and sign token + List authorizedServices = new ArrayList<>(); + authorizedServices.add("sports.fantasy"); + authorizedServices.add("sports.hockey"); + + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime) + .authorizedServices(authorizedServices).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // now let's sign the token for an authorized service + + userTokenToSign.signForAuthorizedService("sports.fantasy", "1", servicePrivateKeyStringK1); + + // Create a token for validation using the signed data + StringBuilder errMsg = new StringBuilder(); + assertEquals(serviceAuthority.validateAuthorizeService(userTokenToSign, errMsg), "sports.fantasy"); + } + + @Test + public void testValidateAuthorizedServiceNoServices() throws IOException { + + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = new KeyStoreMock(); + serviceAuthority.setKeyStore(keyStore); + + long issueTime = System.currentTimeMillis() / 1000; + // Create and sign token + + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // Create a token for validation using the signed data + StringBuilder errMsg = new StringBuilder(); + assertNull(serviceAuthority.validateAuthorizeService(userTokenToSign, errMsg)); + } + + @Test + public void testValidateAuthorizedServiceNoSignature() throws IOException { + + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = new KeyStoreMock(); + serviceAuthority.setKeyStore(keyStore); + + long issueTime = System.currentTimeMillis() / 1000; + // Create and sign token + + List authorizedServices = new ArrayList<>(); + authorizedServices.add("coretech.storage"); + authorizedServices.add("media.storage"); + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).authorizedServices(authorizedServices) + .expirationWindow(expirationTime).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // Create a token for validation using the signed data + StringBuilder errMsg = new StringBuilder(); + assertNull(serviceAuthority.validateAuthorizeService(userTokenToSign, errMsg)); + } + + @Test + public void testGetAuthorizedServiceNameMultipleServices() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + List authorizedServices = new ArrayList<>(); + authorizedServices.add("coretech.storage"); + authorizedServices.add("media.storage"); + assertEquals(serviceAuthority.getAuthorizedServiceName(authorizedServices, null), null); + assertEquals(serviceAuthority.getAuthorizedServiceName(authorizedServices, "sports.storage"), null); + assertEquals(serviceAuthority.getAuthorizedServiceName(authorizedServices, "coretech.storage"), + "coretech.storage"); + } + + @Test + public void testGetAuthorizedServiceNameSingleServices() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + List authorizedServices = new ArrayList<>(); + authorizedServices.add("coretech.storage"); + assertEquals(serviceAuthority.getAuthorizedServiceName(authorizedServices, null), "coretech.storage"); + assertEquals(serviceAuthority.getAuthorizedServiceName(authorizedServices, "sports.storage"), null); + assertEquals(serviceAuthority.getAuthorizedServiceName(authorizedServices, "coretech.storage"), + "coretech.storage"); + } + + @Test + public void testIsWriteOperation() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + assertTrue(serviceAuthority.isWriteOperation("PUT")); + assertTrue(serviceAuthority.isWriteOperation("put")); + assertTrue(serviceAuthority.isWriteOperation("Post")); + assertTrue(serviceAuthority.isWriteOperation("POST")); + assertTrue(serviceAuthority.isWriteOperation("DeLete")); + assertTrue(serviceAuthority.isWriteOperation("DELETE")); + assertFalse(serviceAuthority.isWriteOperation("GET")); + assertFalse(serviceAuthority.isWriteOperation("Get")); + assertFalse(serviceAuthority.isWriteOperation("HEAD")); + assertFalse(serviceAuthority.isWriteOperation(null)); + assertFalse(serviceAuthority.isWriteOperation("Unknown")); + assertFalse(serviceAuthority.isWriteOperation("")); + } + + @Test + public void testGetCredSource() { + PrincipalAuthority authority = new PrincipalAuthority(); + authority.initialize(); + assertEquals(CredSource.HEADER, authority.getCredSource()); + } + + @Test + public void testGetPublicKeyKeyServiceZms() { + + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = Mockito.mock(KeyStore.class); + serviceAuthority.setKeyStore(keyStore); + + Mockito.when(keyStore.getPublicKey("sys.auth", "zms", "v1")).thenReturn("zms-key"); + Mockito.when(keyStore.getPublicKey("athenz", "svc", "v1")).thenReturn("athenz-key"); + + String key = serviceAuthority.getPublicKey("athenz", "svc", "zms", "v1", false); + assertEquals(key, "zms-key"); + } + + @Test + public void testGetPublicKeyKeyServiceZts() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = Mockito.mock(KeyStore.class); + serviceAuthority.setKeyStore(keyStore); + + Mockito.when(keyStore.getPublicKey("sys.auth", "zms", "v1")).thenReturn("zms-key"); + Mockito.when(keyStore.getPublicKey("sys.auth", "zts", "v1")).thenReturn("zts-key"); + Mockito.when(keyStore.getPublicKey("athenz", "svc", "v1")).thenReturn("athenz-key"); + + String key = serviceAuthority.getPublicKey("athenz", "svc", "zts", "v1", false); + assertEquals(key, "zts-key"); + } + + @Test + public void testGetPublicKeyKeyServiceInvalid() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = Mockito.mock(KeyStore.class); + serviceAuthority.setKeyStore(keyStore); + + Mockito.when(keyStore.getPublicKey("sys.auth", "zms", "v1")).thenReturn("zms-key"); + Mockito.when(keyStore.getPublicKey("sys.auth", "zts", "v1")).thenReturn("zts-key"); + Mockito.when(keyStore.getPublicKey("athenz", "svc", "v1")).thenReturn("athenz-key"); + + String key = serviceAuthority.getPublicKey("athenz", "svc", "bondo", "v1", false); + assertEquals(key, "athenz-key"); + } + + @Test + public void testGetPublicKeyKeyServiceEmpty() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = Mockito.mock(KeyStore.class); + serviceAuthority.setKeyStore(keyStore); + + Mockito.when(keyStore.getPublicKey("sys.auth", "zms", "v1")).thenReturn("zms-key"); + Mockito.when(keyStore.getPublicKey("sys.auth", "zts", "v1")).thenReturn("zts-key"); + Mockito.when(keyStore.getPublicKey("athenz", "svc", "v1")).thenReturn("athenz-key"); + + String key = serviceAuthority.getPublicKey("athenz", "svc", "", "v1", false); + assertEquals(key, "athenz-key"); + } + + @Test + public void testGetPublicKeyUserToken() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = Mockito.mock(KeyStore.class); + serviceAuthority.setKeyStore(keyStore); + + Mockito.when(keyStore.getPublicKey("sys.auth", "zms", "v1")).thenReturn("zms-key"); + Mockito.when(keyStore.getPublicKey("sys.auth", "zts", "v1")).thenReturn("zts-key"); + Mockito.when(keyStore.getPublicKey("athenz", "svc", "v1")).thenReturn("athenz-key"); + + String key = serviceAuthority.getPublicKey("athenz", "svc", null, "v1", true); + assertEquals(key, "zms-key"); + } + + @Test + public void testGetPublicKeyDefault() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = Mockito.mock(KeyStore.class); + serviceAuthority.setKeyStore(keyStore); + + Mockito.when(keyStore.getPublicKey("sys.auth", "zms", "v1")).thenReturn("zms-key"); + Mockito.when(keyStore.getPublicKey("sys.auth", "zts", "v1")).thenReturn("zts-key"); + Mockito.when(keyStore.getPublicKey("cd.step", "sd10000", "v1")).thenReturn("cd-key"); + Mockito.when(keyStore.getPublicKey("athenz", "svc", "v1")).thenReturn("athenz-key"); + + String key = serviceAuthority.getPublicKey("athenz", "svc", null, "v1", false); + assertEquals(key, "athenz-key"); + } + + @Test + public void testRemoteIpCheckAll() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + serviceAuthority.ipCheckMode = IpCheckMode.OPS_ALL; + + PrincipalToken serviceToken = new PrincipalToken("v=S1;d=domain;n=service;i=10.11.12.23;s=sig"); + assertTrue(serviceAuthority.remoteIpCheck("10.11.12.23", "GET", serviceToken, null)); + assertTrue(serviceAuthority.remoteIpCheck("10.11.12.23", "PUT", serviceToken, null)); + assertFalse(serviceAuthority.remoteIpCheck("10.11.12.22", "GET", serviceToken, null)); + assertFalse(serviceAuthority.remoteIpCheck("10.11.12.22", "PUT", serviceToken, null)); + } + + @Test + public void testRemoteIpCheckWrite() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + serviceAuthority.ipCheckMode = IpCheckMode.OPS_WRITE; + + PrincipalToken serviceToken = new PrincipalToken("v=S1;d=user;n=user1;i=10.11.12.23;s=sig"); + + // first let's verify read operation with and without matches + + assertTrue(serviceAuthority.remoteIpCheck("10.11.12.23", "GET", serviceToken, null)); + assertTrue(serviceAuthority.remoteIpCheck("10.11.12.22", "GET", serviceToken, null)); + + // now let's try write operations without authorized service + + assertTrue(serviceAuthority.remoteIpCheck("10.11.12.23", "PUT", serviceToken, null)); + assertFalse(serviceAuthority.remoteIpCheck("10.11.12.22", "PUT", serviceToken, null)); + + // finally mismatch operation with authorized service + + assertTrue(serviceAuthority.remoteIpCheck("10.11.12.22", "PUT", serviceToken, "authz_service")); + } + + @Test + public void testRemoteIpCheckNone() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + serviceAuthority.ipCheckMode = IpCheckMode.OPS_NONE; + + PrincipalToken serviceToken = new PrincipalToken("v=S1;d=user;n=user1;i=10.11.12.23;s=sig"); + + // all operations must return true + + // first let's verify read operation with and without matches + + assertTrue(serviceAuthority.remoteIpCheck("10.11.12.23", "GET", serviceToken, null)); + assertTrue(serviceAuthority.remoteIpCheck("10.11.12.22", "GET", serviceToken, null)); + + // now let's try write operations without authorized service + + assertTrue(serviceAuthority.remoteIpCheck("10.11.12.23", "PUT", serviceToken, null)); + assertTrue(serviceAuthority.remoteIpCheck("10.11.12.22", "PUT", serviceToken, null)); + + // finally mismatch operation with authorized service + + assertTrue(serviceAuthority.remoteIpCheck("10.11.12.22", "PUT", serviceToken, "authz_service")); + } + + @Test + public void testAuthenticateIlligal() throws IOException { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + + Principal principal = serviceAuthority.authenticate("aaaa", null, "GET", null); + assertNull(principal); + } + + @Test + public void testInitialize() + throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Class c = PrincipalAuthority.class; + PrincipalAuthority principalAuthority = new PrincipalAuthority(); + System.setProperty(PrincipalAuthority.ATHENZ_PROP_TOKEN_OFFSET, "-1"); + + principalAuthority.initialize(); + Field f1 = c.getDeclaredField("allowedOffset"); + f1.setAccessible(true); + int m = (Integer) f1.get(principalAuthority); + + assertEquals(m, 300); + assertEquals(principalAuthority.userDomain, "user"); + } + + @Test + public void testValidateAuthorizedIlligalServiceName() throws IOException { + + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = new KeyStoreMock(); + serviceAuthority.setKeyStore(keyStore); + + long issueTime = System.currentTimeMillis() / 1000; + // Create and sign token + List authorizedServices = new ArrayList<>(); + authorizedServices.add(".fantasy"); + + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime) + .authorizedServices(authorizedServices).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // now let's sign the token for an authorized service + userTokenToSign.signForAuthorizedService(".fantasy", "1", servicePrivateKeyStringK1); + + // Create a token for validation using the signed data + StringBuilder errMsg = new StringBuilder(); + assertNull(serviceAuthority.validateAuthorizeService(userTokenToSign, errMsg)); + } + + @Test + public void testValidateAuthorizedIlligalForAuthorizedService() throws IOException { + + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = Mockito.mock(KeyStore.class); + serviceAuthority.setKeyStore(keyStore); + + Mockito.when(keyStore.getPublicKey("sports", "fantasy", "1")).thenReturn(null); + + long issueTime = System.currentTimeMillis() / 1000; + // Create and sign token + List authorizedServices = new ArrayList<>(); + authorizedServices.add("sports.fantasy"); + + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime) + .authorizedServices(authorizedServices).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // now let's sign the token for an authorized service + userTokenToSign.signForAuthorizedService("sports.fantasy", "1", servicePrivateKeyStringK1); + + // Create a token for validation using the signed data + StringBuilder errMsg = new StringBuilder(); + assertNull(serviceAuthority.validateAuthorizeService(userTokenToSign, errMsg)); + } + + @Test + public void testPrincipalAuthorityAuthenticateIlligal() throws IOException, CryptoException { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + KeyStore keyStore = Mockito.mock(KeyStore.class); + serviceAuthority.setKeyStore(keyStore); + + String t = "v=S1;d=domain;n=hoge;bs=aaaa;s=signatur"; + + Principal check = serviceAuthority.authenticate(t, "10", "10", null); + assertNull(check); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/RoleAuthorityTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/RoleAuthorityTest.java new file mode 100644 index 00000000000..3e2f126412f --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/RoleAuthorityTest.java @@ -0,0 +1,397 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.testng.Assert.*; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.KeyStore; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.PrincipalAuthority; +import com.yahoo.athenz.auth.impl.RoleAuthority; +import com.yahoo.athenz.auth.token.RoleToken; +import com.yahoo.athenz.auth.util.CryptoException; + +public class RoleAuthorityTest { + + private final String rolVersion = "Z1"; + private final String svcDomain = "sports"; + private final String salt = "aAkjbbDMhnLX"; + private final String testKeyVersionK0 = "0"; + private final String testKeyVersionK1 = "1"; + + private final long expirationTime = 10; // 10 seconds + + private String ztsPrivateKeyStringK0 = null; + private String ztsPrivateKeyStringK1 = null; + private static final String ZMS_USER_DOMAIN = "athenz.user_domain"; + + static String userDomain = System.getProperty(ZMS_USER_DOMAIN, "user"); + + @BeforeTest + private void loadKeys() throws IOException { + + Path path = Paths.get("./src/test/resources/zts_private_k0.key"); + ztsPrivateKeyStringK0 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/zts_private_k1.key"); + ztsPrivateKeyStringK1 = new String(Files.readAllBytes(path)); + } + + private String tamperWithRoleToken(String signedToken) { + String version = null; + String domain = null; + StringBuilder roleNames = null; + String signature = null; + long timestamp = 0; + long expiryTime = 0; + + for (String item : signedToken.split(";")) { + String[] kv = item.split("="); + if (kv.length == 2) { + if ("v".equals(kv[0])) { + version = kv[1]; + } else if ("d".equals(kv[0])) { + domain = kv[1]; + } else if ("r".equals(kv[0])) { + roleNames = new StringBuilder(kv[1]); + } else if ("t".equals(kv[0])) { + timestamp = Long.parseLong(kv[1]); + } else if ("e".equals(kv[0])) { + expiryTime = Long.parseLong(kv[1]); + } else if ("s".equals(kv[0])) { + signature = kv[1]; + } + } + } + + roleNames.append(",storage.tenant.weather.admin"); // tamper here by adding a role + + List roles = Arrays.asList(roleNames.toString().split(",")); + + StringBuilder flattenedRoles = new StringBuilder(); + + int i = 0; + for (String role : roles) { + if (++i == roles.size()) { + flattenedRoles.append(role); + } else { + flattenedRoles.append(role + ","); + } + } + + String tamperedToken = new String("v=" + version + ";d=" + domain + + ";r=" + flattenedRoles + ";t=" + Long.toString(timestamp) + + ";e=" + Long.toString(expiryTime) + ";s=" + signature); + + return tamperedToken; + } + + @Test + public void testRoleAuthority() throws IOException, CryptoException { + RoleAuthority rollAuthority = new RoleAuthority(); + KeyStore keyStore = new KeyStoreMock(); + rollAuthority.setKeyStore(keyStore); + + assertEquals(rollAuthority.getDomain(), "sys.auth"); + assertEquals(rollAuthority.getHeader(), "Athenz-Role-Auth"); + + // Add some roles + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + roles.add("fantasy.tenant.sports.admin"); + roles.add("fantasy.tenant.sports.reader"); + roles.add("fantasy.tenant.sports.writer"); + roles.add("fantasy.tenant.sports.scanner"); + + // Create and sign token with no key version + RoleToken rollToken = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).ip("127.0.0.1").expirationWindow(expirationTime) + .principal("coretech.storage").build(); + rollToken.sign(ztsPrivateKeyStringK0); + + StringBuilder errMsg = new StringBuilder(); + Principal principal = rollAuthority.authenticate(rollToken.getSignedToken(), + "127.0.0.1", "GET", errMsg); + + assertNotNull(principal); + assertNotNull(principal.getAuthority()); + assertEquals(principal.getCredentials(), + rollToken.getSignedToken()); + assertEquals(principal.getDomain(), rollToken.getDomain()); + + principal = rollAuthority.authenticate(rollToken.getSignedToken(), + "127.0.0.1", "GET", null); + assertNotNull(principal); + + List rolesToValidate = principal.getRoles(); + assertEquals(rolesToValidate.size(), roles.size()); + assertTrue(rolesToValidate.equals(roles)); + + // Create and sign token with keyVersion = 0 + rollToken = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).ip("127.0.0.1").expirationWindow(expirationTime) + .principal("coretech.storage").keyId(testKeyVersionK0).build(); + rollToken.sign(ztsPrivateKeyStringK0); + + principal = rollAuthority.authenticate(rollToken.getSignedToken(), + "127.0.0.1", "GET", errMsg); + + assertNotNull(principal); + assertEquals(principal.getCredentials(), + rollToken.getSignedToken()); + + // Create and sign token with keyVersion = 1 + rollToken = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).ip("127.0.0.1").expirationWindow(expirationTime) + .principal("coretech.storage").keyId(testKeyVersionK1).build(); + rollToken.sign(ztsPrivateKeyStringK1); + + principal = rollAuthority.authenticate(rollToken.getSignedToken(), + "127.0.0.1", "GET", errMsg); + + assertNotNull(principal); + assertEquals(principal.getCredentials(), + rollToken.getSignedToken()); + } + + @Test + public void testRoleAuthority_TamperedToken() throws IOException, + CryptoException { + RoleAuthority rollAuthority = new RoleAuthority(); + KeyStore keyStore = new KeyStoreMock(); + rollAuthority.setKeyStore(keyStore); + + // Add some roles + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + roles.add("fantasy.tenant.sports.admin"); + roles.add("fantasy.tenant.sports.reader"); + roles.add("fantasy.tenant.sports.writer"); + roles.add("fantasy.tenant.sports.scanner"); + + // Create and sign token + RoleToken serviceToken = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).ip("127.0.0.1").expirationWindow(expirationTime) + .principal("coretech.storage").keyId(testKeyVersionK1).build(); + serviceToken.sign(ztsPrivateKeyStringK0); + + String tokenToTamper = serviceToken.getSignedToken(); + + StringBuilder errMsg = new StringBuilder(); + Principal principal = rollAuthority.authenticate(tamperWithRoleToken(tokenToTamper), + "127.0.0.1", "GET", errMsg); + + // Role Authority should return null when authenticate() fails + assertNull(principal); + assertTrue(!errMsg.toString().isEmpty()); + assertTrue(errMsg.toString().contains("authenticate")); + + principal = rollAuthority.authenticate(tamperWithRoleToken(tokenToTamper), + "127.0.0.1", "GET", null); + assertNull(principal); + } + + @Test + public void testRoleAuthorityMismatchIPNonUser() throws IOException, CryptoException { + RoleAuthority rollAuthority = new RoleAuthority(); + KeyStore keyStore = new KeyStoreMock(); + rollAuthority.setKeyStore(keyStore); + + // Add some roles + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + + // Create and sign token with keyVersion = 0 + RoleToken roleToken = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).ip("127.0.0.1").expirationWindow(expirationTime) + .principal("coretech.storage").keyId(testKeyVersionK0).build(); + roleToken.sign(ztsPrivateKeyStringK0); + + // mismatch IP but should be OK since it's not User + StringBuilder errMsg = new StringBuilder(); + Principal principal = rollAuthority.authenticate(roleToken.getSignedToken(), + "127.0.0.2", "GET", errMsg); + + assertNotNull(principal); + } + + @Test + public void testRoleAuthorityMismatchIPNonWrite() throws IOException, CryptoException { + RoleAuthority rollAuthority = new RoleAuthority(); + KeyStore keyStore = new KeyStoreMock(); + rollAuthority.setKeyStore(keyStore); + + // Add some roles + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + + // Create and sign token with keyVersion = 0 + RoleToken roleToken = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).ip("127.0.0.1").expirationWindow(expirationTime) + .principal("" + userDomain + ".joe").keyId(testKeyVersionK0).build(); + roleToken.sign(ztsPrivateKeyStringK0); + + // mismatch IP but should be OK since it's not write operation + StringBuilder errMsg = new StringBuilder(); + Principal principal = rollAuthority.authenticate(roleToken.getSignedToken(), + "127.0.0.2", "GET", errMsg); + + assertNotNull(principal); + } + + @Test + public void testRoleAuthorityMismatchIP() throws IOException, CryptoException { + RoleAuthority rollAuthority = new RoleAuthority(); + KeyStore keyStore = new KeyStoreMock(); + rollAuthority.setKeyStore(keyStore); + + // Add some roles + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + + // Create and sign token with keyVersion = 0 + RoleToken roleToken = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).ip("127.0.0.1").expirationWindow(expirationTime) + .principal("" + userDomain + ".joe").keyId(testKeyVersionK0).build(); + roleToken.sign(ztsPrivateKeyStringK0); + + // mismatch IP should fail + StringBuilder errMsg = new StringBuilder(); + Principal principal = rollAuthority.authenticate(roleToken.getSignedToken(), + "127.0.0.2", "DELETE", errMsg); + + assertNull(principal); + assertTrue(!errMsg.toString().isEmpty()); + assertTrue(errMsg.toString().contains("authenticate")); + + errMsg = new StringBuilder(); // get a fresh one + principal = rollAuthority.authenticate(roleToken.getSignedToken(), + "127.0.0.2", "PUT", errMsg); + + assertNull(principal); + assertTrue(!errMsg.toString().isEmpty()); + assertTrue(errMsg.toString().contains("authenticate")); + + // final check should be ok with valid IP + principal = rollAuthority.authenticate(roleToken.getSignedToken(), + "127.0.0.1", "DELETE", errMsg); + + assertNotNull(principal); + } + + @Test + public void testIsWriteOperation() { + PrincipalAuthority serviceAuthority = new PrincipalAuthority(); + assertTrue(serviceAuthority.isWriteOperation("PUT")); + assertTrue(serviceAuthority.isWriteOperation("put")); + assertTrue(serviceAuthority.isWriteOperation("Post")); + assertTrue(serviceAuthority.isWriteOperation("POST")); + assertTrue(serviceAuthority.isWriteOperation("DeLete")); + assertTrue(serviceAuthority.isWriteOperation("DELETE")); + assertFalse(serviceAuthority.isWriteOperation("GET")); + assertFalse(serviceAuthority.isWriteOperation("Get")); + assertFalse(serviceAuthority.isWriteOperation("HEAD")); + assertFalse(serviceAuthority.isWriteOperation(null)); + assertFalse(serviceAuthority.isWriteOperation("Unknown")); + assertFalse(serviceAuthority.isWriteOperation("")); + } + + @Test + public void testAuthenticateIlligal() throws IOException { + RoleAuthority roleAuthority = new RoleAuthority(); + roleAuthority.initialize(); + + Principal principal = roleAuthority.authenticate("", "10.72.118.45", "GET", null); + assertNull(principal); + + KeyStore keyStore = new KeyStoreMock(); + roleAuthority.setKeyStore(keyStore); + + // Add some roles + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + + // Create and sign token with keyVersion = 0 + RoleToken roleToken = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).ip("127.0.0.1").expirationWindow(expirationTime) + .principal(".").keyId(testKeyVersionK0).build(); + roleToken.sign(ztsPrivateKeyStringK0); + + principal = roleAuthority.authenticate(roleToken.getSignedToken(), + "127.0.0.2", "DELETE", null); + assertNull(principal); + + roleToken = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).ip("127.0.0.1").expirationWindow(expirationTime) + .principal("illigal.joe").keyId(testKeyVersionK0).build(); + roleToken.sign(ztsPrivateKeyStringK0); + + principal = roleAuthority.authenticate(roleToken.getSignedToken(), + "127.0.0.2", "DELETE", null); + assertNotNull(principal); + } + + @Test + public void testIsWriteOperationNull() throws IOException { + RoleAuthority roleAuthority = new RoleAuthority(); + roleAuthority.initialize(); + + KeyStore keyStore = new KeyStoreMock(); + roleAuthority.setKeyStore(keyStore); + + // Add some roles + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + + // Create and sign token with keyVersion = 0 + RoleToken roleToken = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).ip("127.0.0.1").expirationWindow(expirationTime) + .principal(".").keyId(testKeyVersionK0).build(); + roleToken.sign(ztsPrivateKeyStringK0); + + Principal principal = roleAuthority.authenticate(roleToken.getSignedToken(), + "127.0.0.2", null, null); + assertNotNull(principal); + } + + @Test + public void testInitialize() throws NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException { + Class c = RoleAuthority.class; + RoleAuthority roleAuthority = new RoleAuthority(); + System.setProperty(RoleAuthority.ATHENZ_PROP_TOKEN_OFFSET, "-1"); + + roleAuthority.initialize(); + + Field f1 = c.getDeclaredField("allowedOffset"); + f1.setAccessible(true); + int m = (Integer) f1.get(roleAuthority); + + assertEquals(m,300); + assertEquals(roleAuthority.userDomain,"user"); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/SimplePrincipalTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/SimplePrincipalTest.java new file mode 100644 index 00000000000..441b37fe593 --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/SimplePrincipalTest.java @@ -0,0 +1,253 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import static org.testng.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.impl.UserAuthority; + +public class SimplePrincipalTest { + + String fakeUnsignedCreds = "v=U1;d=user;n=jdoe"; + String fakeCreds = fakeUnsignedCreds + ";s=signature"; + + @Test + public void testSimplePrincipal() { + SimplePrincipal p = (SimplePrincipal) SimplePrincipal.create("user", "jdoe", fakeCreds, null); + assertNotNull(p); + p.setUnsignedCreds(fakeUnsignedCreds); + assertEquals(p.getName(), "jdoe"); + assertEquals(p.getDomain(), "user"); + assertEquals(p.getCredentials(), fakeCreds); + assertEquals(p.getUnsignedCredentials(), fakeUnsignedCreds); + } + + @Test + public void testSimplePrincipalNullUnsignedCred() { + Principal p = SimplePrincipal.create("user", "jdoe", fakeCreds, null); + assertNotNull(p); + assertEquals(p.getName(), "jdoe"); + assertEquals(p.getDomain(), "user"); + assertEquals(p.getCredentials(), fakeCreds); + assertNull(p.getUnsignedCredentials()); + } + + @Test + public void testYRN() { + Principal p = SimplePrincipal.create("user", "jdoe", fakeCreds, null); + assertEquals(p.getYRN(), "user.jdoe"); + + p = SimplePrincipal.create(null, "jdoe", fakeCreds); + assertEquals(p.getYRN(), "jdoe"); + + List roles = new ArrayList(); + roles.add("role1"); + + p = SimplePrincipal.create("user", fakeCreds, roles, null); + assertEquals(p.getYRN(), "user"); + + Authority a = null; + p = SimplePrincipal.create(null, null, a); + assertNull(p.getYRN()); + } + + @Test + public void testSimplePrincipalInvalidDomain() { + + // we output warning but still create a principal + + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + + UserAuthority userAuthority = new UserAuthority(); + userAuthority.initialize(); + + SimplePrincipal p = (SimplePrincipal) SimplePrincipal.create("user test invalid#$%", + fakeCreds, roles, userAuthority); + assertNotNull(p); + p.setUnsignedCreds(fakeUnsignedCreds); + assertEquals(p.getDomain(), "user test invalid#$%"); + assertEquals(p.getCredentials(), fakeCreds); + assertEquals(p.getUnsignedCredentials(), fakeUnsignedCreds); + } + + @Test + public void testSimplePrincipalNullRole() { + + // we output warning but still create a principal + + UserAuthority userAuthority = new UserAuthority(); + userAuthority.initialize(); + + SimplePrincipal p = (SimplePrincipal) SimplePrincipal.create("user", + fakeCreds, (List) null, userAuthority); + assertNotNull(p); + p.setUnsignedCreds(fakeUnsignedCreds); + assertEquals(p.getDomain(), "user"); + assertEquals(p.getCredentials(), fakeCreds); + assertEquals(p.getUnsignedCredentials(), fakeUnsignedCreds); + } + + @Test + public void testSimplePrincipalEmptyRole() { + + // we output warning but still create a principal + + List roles = new ArrayList(); + + UserAuthority userAuthority = new UserAuthority(); + userAuthority.initialize(); + + SimplePrincipal p = (SimplePrincipal) SimplePrincipal.create("user", fakeCreds, roles, userAuthority); + assertNotNull(p); + p.setUnsignedCreds(fakeUnsignedCreds); + assertEquals(p.getDomain(), "user"); + assertEquals(p.getCredentials(), fakeCreds); + assertEquals(p.getUnsignedCredentials(), fakeUnsignedCreds); + } + + @Test + public void testSimplePrincipalInvalidName() { + + // we output warning but still create a principal + + UserAuthority userAuthority = new UserAuthority(); + userAuthority.initialize(); + + SimplePrincipal p = (SimplePrincipal) SimplePrincipal.create("user", "jdoe invalid#$%", + fakeCreds, 0, userAuthority); + assertNotNull(p); + p.setUnsignedCreds(fakeUnsignedCreds); + assertEquals(p.getDomain(), "user"); + assertEquals(p.getName(), "jdoe invalid#$%"); + assertEquals(p.getCredentials(), fakeCreds); + assertEquals(p.getUnsignedCredentials(), fakeUnsignedCreds); + } + + @Test + public void testSimplePrincipalNullDomainAuthorityDomainNotNull() { + + // we output warning but still create a principal + + UserAuthority userAuthority = new UserAuthority(); + userAuthority.initialize(); + + Principal p = SimplePrincipal.create(null, "jdoe", fakeCreds, 0, userAuthority); + assertNull(p); + } + + @Test + public void testSimplePrincipalNullDomainAuthorityDomainNull() { + + // we output warning but still create a principal + + UserAuthority userAuthority = new UserAuthority(); + userAuthority.initialize(); + + Principal p = SimplePrincipal.create(null, "jdoe", fakeCreds, 0, null); + assertNull(p.getDomain()); + } + + @Test + public void testSimplePrincipalDomainNoMatch() { + + // we output warning but still create a principal + + UserAuthority userAuthority = new UserAuthority(); + userAuthority.initialize(); + + Principal p = SimplePrincipal.create("coretech", "jdoe", fakeCreds, 0, userAuthority); + assertNull(p); + } + + @Test + public void testSimplePrincipalIssueTime() { + + UserAuthority userAuthority = new UserAuthority(); + userAuthority.initialize(); + + Principal p = SimplePrincipal.create("user", "jdoe", fakeCreds, 101, userAuthority); + assertNotNull(p); + assertEquals(p.getIssueTime(), 101); + } + + @Test + public void testSimplePrincipalToStringZToken() { + + List roles = new ArrayList(); + roles.add("updater"); + + UserAuthority userAuthority = new UserAuthority(); + userAuthority.initialize(); + + Principal p = SimplePrincipal.create("user", fakeCreds, roles, userAuthority); + assertEquals(p.toString(), "ZToken_user~updater"); + } + + @Test + public void testSimplePrincipalToStringUser() { + + UserAuthority userAuthority = new UserAuthority(); + userAuthority.initialize(); + + Principal p = SimplePrincipal.create("user", "jdoe", fakeCreds, 101, userAuthority); + assertEquals(p.toString(), "user.jdoe"); + } + + @Test + public void testSimplePrincipalExtraFields() { + + UserAuthority userAuthority = new UserAuthority(); + userAuthority.initialize(); + + Principal p = SimplePrincipal.create("user", "jdoe", fakeCreds, 101, userAuthority); + ((SimplePrincipal) p).setOriginalRequestor("athenz.ci"); + ((SimplePrincipal) p).setKeyService("zts"); + + assertEquals(p.toString(), "user.jdoe"); + assertEquals(p.getOriginalRequestor(), "athenz.ci"); + assertEquals(p.getKeyService(), "zts"); + } + + @Test + public void testSimplePrincipalIP() { + SimplePrincipal check = (SimplePrincipal) SimplePrincipal.create("user", "jdoe", "hoge"); + + check.setIP("10.10.10.10"); + assertEquals(check.getIP(),"10.10.10.10"); + } + + @Test + public void testSimplePrincipalAuthorityCreate() { + Authority hoge = Mockito.mock(Authority.class); + + SimplePrincipal check = (SimplePrincipal) SimplePrincipal.create("user", "jdoe", hoge); + assertNotNull(check); + + Mockito.when(hoge.getDomain()).thenReturn(null); + check = (SimplePrincipal) SimplePrincipal.create(null, "jdoe","hoge", 0, hoge); + assertNotNull(check); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/SimpleServiceIdentityProviderTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/SimpleServiceIdentityProviderTest.java new file mode 100644 index 00000000000..a8d5e8f0626 --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/SimpleServiceIdentityProviderTest.java @@ -0,0 +1,132 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.PrincipalAuthority; +import com.yahoo.athenz.auth.impl.SimpleServiceIdentityProvider; +import com.yahoo.athenz.auth.token.PrincipalToken; +import com.yahoo.athenz.auth.util.Crypto; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; + +import static org.testng.Assert.*; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +public class SimpleServiceIdentityProviderTest { + + private String servicePublicKeyStringK0 = null; + private String servicePublicKeyStringK1 = null; + private String servicePrivateKeyStringK1 = null; + private File k0File = null; + private Authority authority = null; + + @BeforeTest + private void loadKeys() throws IOException { + Path path = Paths.get("./src/test/resources/fantasy_public_k0.key"); + servicePublicKeyStringK0 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/fantasy_private_k0.key"); + k0File = path.toFile(); + + path = Paths.get("./src/test/resources/fantasy_public_k1.key"); + servicePublicKeyStringK1 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/fantasy_private_k1.key"); + servicePrivateKeyStringK1 = new String(Files.readAllBytes(path)); + + authority = new PrincipalAuthority(); + } + + @Test + public void testSimpleIdentityDefaultV0() { + + SimpleServiceIdentityProvider provider = new SimpleServiceIdentityProvider(authority, k0File); + Principal user = provider.getIdentity("coretech", "athenz"); + assertNotNull(user); + assertTrue(user.getIssueTime() != 0); + + String token = user.getCredentials(); + PrincipalToken prToken = new PrincipalToken(token); + assertTrue(prToken.validate(servicePublicKeyStringK0, 0)); + assertEquals(prToken.getKeyId(), "0"); + } + + @Test + public void testSimpleIdentityDefaultV1() { + + PrivateKey key = Crypto.loadPrivateKey(servicePrivateKeyStringK1); + SimpleServiceIdentityProvider provider = new SimpleServiceIdentityProvider(authority, key, "1", 3600); + Principal user = provider.getIdentity("coretech", "athenz"); + assertNotNull(user); + assertTrue(user.getIssueTime() != 0); + + String token = user.getCredentials(); + PrincipalToken prToken = new PrincipalToken(token); + assertTrue(prToken.validate(servicePublicKeyStringK1, 0)); + assertEquals(prToken.getKeyId(), "1"); + } + + @Test + public void testSimpleIdentityPrivateKey() { + + PrivateKey privateKey = Crypto.loadPrivateKey(k0File); + + SimpleServiceIdentityProvider provider = new SimpleServiceIdentityProvider(authority, privateKey); + Principal user = provider.getIdentity("coretech", "athenz"); + assertNotNull(user); + assertTrue(user.getIssueTime() != 0); + + String token = user.getCredentials(); + PrincipalToken prToken = new PrincipalToken(token); + assertTrue(prToken.validate(servicePublicKeyStringK0, 0)); + assertEquals(prToken.getKeyId(), "0"); + + provider = new SimpleServiceIdentityProvider(authority, privateKey, 3600); + user = provider.getIdentity("coretech", "athenz"); + assertNotNull(user); + assertTrue(user.getIssueTime() != 0); + + token = user.getCredentials(); + prToken = new PrincipalToken(token); + assertTrue(prToken.validate(servicePublicKeyStringK0, 0)); + assertEquals(prToken.getKeyId(), "0"); + } + + @Test + public void testGetHost() { + PrivateKey privateKey = Crypto.loadPrivateKey(k0File); + + SimpleServiceIdentityProvider provider = new SimpleServiceIdentityProvider(authority, privateKey); + + String name = provider.getHost(); + assertNotNull(name); + } + + @Test + public void testSimpleIdentityString() { + SimpleServiceIdentityProvider provider = new SimpleServiceIdentityProvider(authority, "aaaaaaaa"); + assertNotNull(provider); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/TestLoginCallbackHandler.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/TestLoginCallbackHandler.java new file mode 100644 index 00000000000..c9f284283bb --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/TestLoginCallbackHandler.java @@ -0,0 +1,75 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +/** + * Password callback handler for resolving password/usernames for a JAAS login. + */ +public class TestLoginCallbackHandler implements CallbackHandler { + + public TestLoginCallbackHandler() { + super(); + } + + public TestLoginCallbackHandler(String name, String password) { + super(); + this.username = name; + if (password != null) { + this.password = password; + } + } + + public TestLoginCallbackHandler(String password) { + super(); + this.password = password; + } + + private String username; + private String password = "orange2"; + + /** + * Handles the callbacks, and sets the user/password detail. + * @param callbacks the callbacks to handle + * @throws Exception if an input or output error occurs. + */ + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + + for (Callback cback: callbacks) { + if (cback instanceof NameCallback && username != null) { + NameCallback nc = (NameCallback) cback; + nc.setName(username); + } else if (cback instanceof PasswordCallback) { + PasswordCallback pc = (PasswordCallback) cback; + pc.setPassword(password.toCharArray()); + } else { + // other callbacks: AuthorizeCallback, ChoiceCallback, ConfirmationCallback, + // LanguageCallback, RealmCallback, RealmChoiceCallback, TextInputCallback, TextOutputCallback + // what does our grid services usually do? + + throw new UnsupportedCallbackException(cback, "Unrecognized Callback"); + } + } + } +} + diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/UserAuthorityTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/UserAuthorityTest.java new file mode 100644 index 00000000000..0ed0356c8f8 --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/UserAuthorityTest.java @@ -0,0 +1,86 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.impl; + +import static org.testng.Assert.*; + +import org.jvnet.libpam.PAM; +import org.jvnet.libpam.PAMException; +import org.jvnet.libpam.UnixUser; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Principal; + +import org.mockito.Mockito; + +public class UserAuthorityTest { + private String testToken = "Basic dGVzdHVzZXI6dGVzdHB3ZA=="; + private String expectedDomain = "user"; + private String expectedHeader = "Authorization"; + private String expectedUserId = "testuser"; + + @Test + public void testUserAuthority() throws PAMException { + PAM pam = Mockito.mock(PAM.class); + UnixUser user = new UnixUser(System.getenv("USER")); + Mockito.when(pam.authenticate("testuser", "testpwd")).thenReturn(user); + UserAuthority userAuthority = new UserAuthority(); + userAuthority.setPAM(pam); + assertEquals(userAuthority.getDomain(), expectedDomain); + assertEquals(userAuthority.getHeader(), expectedHeader); + + StringBuilder errMsg = new StringBuilder(); + Principal principal = userAuthority.authenticate(testToken, "10.72.118.45", "GET", errMsg); + + assertNotNull(principal); + assertNotNull(principal.getAuthority()); + assertEquals(principal.getCredentials(), testToken); + assertEquals(principal.getDomain(), expectedDomain); + assertEquals(principal.getName(), expectedUserId); + } + + @Test + public void testUserAuthorityInvalidFormat() { + PAM pam = Mockito.mock(PAM.class); + UserAuthority userAuthority = new UserAuthority(); + userAuthority.setPAM(pam); + StringBuilder errMsg = new StringBuilder(); + Principal principal = userAuthority.authenticate("dGVzdHVzZXI6dGVzdHB3ZA==", "10.72.118.45", "GET", errMsg); + assertNull(principal); + } + + @Test + public void testAllowAuthorization() { + PAM pam = Mockito.mock(PAM.class); + UserAuthority userAuthority = new UserAuthority(); + userAuthority.setPAM(pam); + + assertFalse(userAuthority.allowAuthorization()); + } + + @Test + public void testAuthenticateException() throws PAMException { + PAM pam = Mockito.mock(PAM.class); + UserAuthority userAuthority = new UserAuthority(); + userAuthority.setPAM(pam); + Mockito.when(pam.authenticate("testuser", "testpwd")).thenReturn(null); + Principal principal = userAuthority.authenticate("Basic dGVzdHVzZXI6dGVzdHB3ZA==", "10.72.118.45", "GET", null); + + principal = userAuthority.authenticate("Basic ", "10.72.118.45", "GET", null); + + assertNull(principal); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/PrincipalTokenTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/PrincipalTokenTest.java new file mode 100644 index 00000000000..b47e1ecfa09 --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/PrincipalTokenTest.java @@ -0,0 +1,724 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.token; + +import static org.testng.Assert.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.util.CryptoException; + +public class PrincipalTokenTest { + + private final String svcVersion = "S1"; + private final String usrVersion = "U1"; + private final String svcDomain = "sports"; + private final String svcName = "fantasy"; + private final String usrDomain = "user"; + private final String usrName = "john"; + private final String host = "somehost.somecompany.com"; + private final String salt = "saltvalue"; + private final String testKeyVersionK1 = "1"; + + private final long expirationTime = 10; // 10 seconds + + private String servicePublicKeyStringK0 = null; + private String servicePrivateKeyStringK0 = null; + private String servicePublicKeyStringK1 = null; + private String servicePrivateKeyStringK1 = null; + + @BeforeTest + private void loadKeys() throws IOException { + Path path = Paths.get("./src/test/resources/fantasy_public_k0.key"); + servicePublicKeyStringK0 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/fantasy_private_k0.key"); + servicePrivateKeyStringK0 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/fantasy_public_k1.key"); + servicePublicKeyStringK1 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/fantasy_private_k1.key"); + servicePrivateKeyStringK1 = new String(Files.readAllBytes(path)); + } + + //backwards compatibility + private PrincipalToken createServiceToken() throws CryptoException { + // Create and sign token + PrincipalToken serviceTokenToSign = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .host(host).salt(salt).expirationWindow(expirationTime).build(); + + serviceTokenToSign.sign(servicePrivateKeyStringK0); + + // Create a token for validation using the signed data + return new PrincipalToken(serviceTokenToSign.getSignedToken()); + } + + private PrincipalToken createServiceToken(String keyVersion) throws CryptoException { + // Create and sign token + PrincipalToken serviceTokenToSign = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .host(host).salt(salt).expirationWindow(expirationTime).keyId(keyVersion).build(); + + String privateKey = null; + if ("0".equals(keyVersion)) { + privateKey = servicePrivateKeyStringK0; + } else if ("1".equals(keyVersion)) { + privateKey = servicePrivateKeyStringK1; + } + + serviceTokenToSign.sign(privateKey); + + // Create a token for validation using the signed data + return new PrincipalToken(serviceTokenToSign.getSignedToken()); + } + + private PrincipalToken createUserToken(long issueTime) + throws CryptoException { + // Create and sign token + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // Create a token for validation using the signed data + return new PrincipalToken(userTokenToSign.getSignedToken()); + } + + @Test + public void testServiceToken() throws InterruptedException, CryptoException { + PrincipalToken serviceTokenToValidate = createServiceToken(testKeyVersionK1); + + // Validate all input data + assertEquals(serviceTokenToValidate.getVersion(), svcVersion); + assertEquals(serviceTokenToValidate.getDomain(), svcDomain); + assertEquals(serviceTokenToValidate.getName(), svcName); + assertEquals(serviceTokenToValidate.getHost(), host); + assertEquals(serviceTokenToValidate.getSalt(), salt); + assertEquals(serviceTokenToValidate.getKeyId(), testKeyVersionK1); + + // Validate the signature and that expiration time had not elapsed + assertTrue(serviceTokenToValidate.validate(servicePublicKeyStringK1, 300)); + + // Create ServiceToken with null keyVersion which should default to 0 + serviceTokenToValidate = createServiceToken(); + assertEquals(serviceTokenToValidate.getKeyId(), "0"); + + // Validate the signature using key(k0) and that expiration time had not elapsed + assertTrue(serviceTokenToValidate.validate(servicePublicKeyStringK0, 300)); + + PrincipalToken svcToken2 = new PrincipalToken(serviceTokenToValidate.getSignedToken()); + assertEquals(svcToken2.getSignedToken(), serviceTokenToValidate.getSignedToken()); + } + + + @Test + public void testUserToken() throws InterruptedException, CryptoException { + long issueTime = System.currentTimeMillis() / 1000; + PrincipalToken userTokenToValidate = createUserToken(issueTime); + + // Validate all input data + assertEquals(userTokenToValidate.getVersion(), usrVersion); + assertEquals(userTokenToValidate.getDomain(), usrDomain); + assertEquals(userTokenToValidate.getName(), usrName); + assertNull(userTokenToValidate.getHost()); + assertEquals(userTokenToValidate.getSalt(), salt); + + // Validate the signature and that expiration time had not elapsed + assertTrue(userTokenToValidate.validate(servicePublicKeyStringK0, 300)); + } + + @Test + public void testTokenStringConstructor() { + long issueTime = System.currentTimeMillis() / 1000; + PrincipalToken pToken = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime).build(); + + pToken.sign(servicePrivateKeyStringK0); // now its signed + String signedToken = pToken.getSignedToken(); + PrincipalToken sigToken1 = new PrincipalToken(signedToken); + assertEquals(sigToken1.getSignedToken(), signedToken); + + String unsignedTok = pToken.getUnsignedToken(); + String signature = pToken.getSignature(); + String newSignedToken = unsignedTok + ";s=" + signature; + assertEquals(newSignedToken, signedToken); + + // instantiate a Token with the unsigned token + signature + PrincipalToken sigToken2 = new PrincipalToken(newSignedToken); + assertEquals(sigToken2.getSignedToken(), signedToken); + assertEquals(sigToken2.getUnsignedToken(), unsignedTok); + assertEquals(sigToken2.getSignature(), signature); + } + + @Test + public void testUserToken_Expired() throws InterruptedException, + CryptoException { + long issueTime = System.currentTimeMillis() / 1000 - 10; + PrincipalToken userTokenToValidate = createUserToken(issueTime); + + // Let expiration time elapse + Thread.sleep(expirationTime * 1000); + + // Validate that the expiration time has elapsed + assertFalse(userTokenToValidate.validate(servicePublicKeyStringK0, 300)); + } + + @Test + public void testServiceToken_Expired() throws InterruptedException, + CryptoException { + PrincipalToken serviceTokenToValidate = createServiceToken("0"); + + // Let expiration time elapse + Thread.sleep((expirationTime + 10) * 1000); + + // Validate that the expiration time has elapsed + assertFalse(serviceTokenToValidate + .validate(servicePublicKeyStringK0, 300)); + } + + @Test + public void testEmptyToken() { + + try { + @SuppressWarnings("unused") + PrincipalToken token = new PrincipalToken(""); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testNullToken() { + + try { + @SuppressWarnings("unused") + PrincipalToken token = new PrincipalToken(null); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testTokenDomainNull() { + + try { + @SuppressWarnings("unused") + PrincipalToken token = new PrincipalToken("v=S1;n=storage;s=sig"); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testTokenDomainEmpty() { + + try { + @SuppressWarnings("unused") + PrincipalToken token = new PrincipalToken("v=S1;d=;n=storage;s=sig"); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void tesTokenNameNull() { + + try { + @SuppressWarnings("unused") + PrincipalToken token = new PrincipalToken("v=S1;d=coretech;s=sig"); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testTokenNameEmpty() { + + try { + @SuppressWarnings("unused") + PrincipalToken token = new PrincipalToken("v=S1;d=coretech;n=;s=sig"); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testTokenWithoutSignature() { + + PrincipalToken token = new PrincipalToken("v=S1;d=coretech;n=storage"); + assertEquals(token.getDomain(), "coretech"); + assertEquals(token.getName(), "storage"); + assertEquals(token.getVersion(), "S1"); + assertNull(token.getUnsignedToken()); + } + + @Test + public void testTokenInvalidVersionValue() { + + PrincipalToken token = new PrincipalToken("v=S1=S2;d=coretech;n=storage"); + assertEquals(token.getName(), "storage"); + assertEquals(token.getDomain(), "coretech"); + assertNull(token.getVersion()); + } + + @Test + public void testBuilderRequiredVersionNull() { + + try { + @SuppressWarnings("unused") + PrincipalToken.Builder builder = new PrincipalToken.Builder(null, svcDomain, svcName); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderRequiredVersionEmptyString() { + + try { + @SuppressWarnings("unused") + PrincipalToken.Builder builder = new PrincipalToken.Builder("", svcDomain, svcName); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderRequiredDomainNull() { + + try { + @SuppressWarnings("unused") + PrincipalToken.Builder builder = new PrincipalToken.Builder(svcVersion, null, svcName); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderRequiredDomainEmptyString() { + + try { + @SuppressWarnings("unused") + PrincipalToken.Builder builder = new PrincipalToken.Builder(svcVersion, "", svcName); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderRequiredNameNull() { + + try { + @SuppressWarnings("unused") + PrincipalToken.Builder builder = new PrincipalToken.Builder(svcVersion, svcDomain, null); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderRequiredNameEmptyString() { + + try { + @SuppressWarnings("unused") + PrincipalToken.Builder builder = new PrincipalToken.Builder(svcVersion, svcDomain, ""); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderDefaultOptionalValues() { + + PrincipalToken token = new PrincipalToken.Builder(svcVersion, svcDomain, svcName).build(); + assertEquals(token.getVersion(), svcVersion); + assertEquals(token.getDomain(), svcDomain); + assertEquals(token.getName(), svcName); + assertNull(token.getHost()); + assertNotNull(token.getSalt()); + assertEquals(token.getKeyId(), "0"); + assertNull(token.getIP()); + long timestamp = token.getTimestamp(); + assertTrue(timestamp != 0); + assertEquals(token.getExpiryTime(), timestamp + 3600); + } + + @Test + public void testBuilderAllValues() { + + PrincipalToken token = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .issueTime(36000).expirationWindow(100).host("localhost").ip("127.0.0.1") + .salt("salt").keyId("zone1").keyService("zts").originalRequestor("athenz.ci.service").build(); + + assertEquals(token.getVersion(), svcVersion); + assertEquals(token.getDomain(), svcDomain); + assertEquals(token.getName(), svcName); + assertEquals(token.getHost(), "localhost"); + assertEquals(token.getSalt(), "salt"); + assertEquals(token.getKeyId(), "zone1"); + assertEquals(token.getIP(), "127.0.0.1"); + assertEquals(token.getKeyService(), "zts"); + assertEquals(token.getOriginalRequestor(), "athenz.ci.service"); + assertEquals(token.getTimestamp(), 36000); + assertEquals(token.getExpiryTime(), 36000 + 100); + } + + @Test + public void testUserTokenWithSingleAuthorizedService() throws InterruptedException, CryptoException { + long issueTime = System.currentTimeMillis() / 1000; + // Create and sign token + List authorizedServices = new ArrayList<>(); + authorizedServices.add("coretech.storage"); + + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime) + .authorizedServices(authorizedServices).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // now let's sign the token for an authorized service + + userTokenToSign.signForAuthorizedService("coretech.storage", "1", servicePrivateKeyStringK1); + + // Create a token for validation using the signed data + + PrincipalToken userTokenToValidate = new PrincipalToken(userTokenToSign.getSignedToken()); + + // Validate all input data + assertEquals(userTokenToValidate.getVersion(), usrVersion); + assertEquals(userTokenToValidate.getDomain(), usrDomain); + assertEquals(userTokenToValidate.getName(), usrName); + assertNull(userTokenToValidate.getHost()); + assertEquals(userTokenToValidate.getSalt(), salt); + assertEquals(userTokenToValidate.getAuthorizedServices(), authorizedServices); + assertEquals(userTokenToValidate.getAuthorizedServiceKeyId(), "1"); + // authorized service name must be null since there is only 1 entry + // in the authorized services list so there must be a match + assertEquals(userTokenToValidate.getAuthorizedServiceName(), null); + + // Validate the signature and that expiration time had not elapsed + assertTrue(userTokenToValidate.validateForAuthorizedService(servicePublicKeyStringK1, null)); + } + + @Test + public void testUserTokenWithMultipleAuthorizedServices() throws InterruptedException, CryptoException { + long issueTime = System.currentTimeMillis() / 1000; + // Create and sign token + List authorizedServices = new ArrayList<>(); + authorizedServices.add("coretech.storage"); + authorizedServices.add("media.storage"); + + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime) + .authorizedServices(authorizedServices).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // now let's sign the token for an authorized service + + userTokenToSign.signForAuthorizedService("coretech.storage", "1", servicePrivateKeyStringK1); + + // Create a token for validation using the signed data + + PrincipalToken userTokenToValidate = new PrincipalToken(userTokenToSign.getSignedToken()); + + // Validate all input data + assertEquals(userTokenToValidate.getVersion(), usrVersion); + assertEquals(userTokenToValidate.getDomain(), usrDomain); + assertEquals(userTokenToValidate.getName(), usrName); + assertNull(userTokenToValidate.getHost()); + assertEquals(userTokenToValidate.getSalt(), salt); + assertEquals(userTokenToValidate.getAuthorizedServices(), authorizedServices); + assertEquals(userTokenToValidate.getAuthorizedServiceKeyId(), "1"); + // since we have 2 authorized services, the name must be specified that + // tells us which service is signing the token + assertEquals(userTokenToValidate.getAuthorizedServiceName(), "coretech.storage"); + + // Validate the signature and that expiration time had not elapsed + StringBuilder errMsg = new StringBuilder(); + assertTrue(userTokenToValidate.validateForAuthorizedService(servicePublicKeyStringK1, errMsg)); + } + + @Test + public void testUserTokenWithNullAuthorizedService() throws InterruptedException, CryptoException { + long issueTime = System.currentTimeMillis() / 1000; + + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime) + .build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // now let's sign the token for an authorized service. we must get + // back illegal argument exception + + try { + userTokenToSign.signForAuthorizedService("coretech.storage", "0", servicePrivateKeyStringK0); + fail(); + } catch (Exception ex) { + assertEquals(ex.getClass(), IllegalArgumentException.class); + } + } + + @Test + public void testUserTokenWithInvalidAuthorizedService() throws InterruptedException, CryptoException { + long issueTime = System.currentTimeMillis() / 1000; + // Create and sign token + List authorizedServices = new ArrayList<>(); + authorizedServices.add("coretech.storage"); + + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime) + .authorizedServices(authorizedServices).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // now let's sign the token for an authorized service. we must get + // back illegal argument exception because of service name mismatch + + try { + userTokenToSign.signForAuthorizedService("weather.storage", "0", servicePrivateKeyStringK0); + fail(); + } catch (Exception ex) { + assertEquals(ex.getClass(), IllegalArgumentException.class); + } + + authorizedServices.add("media.storage"); + PrincipalToken userTokenToSign2 = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime) + .authorizedServices(authorizedServices).build(); + + userTokenToSign2.sign(servicePrivateKeyStringK0); + + // now let's sign the token for an authorized service. we must get + // back illegal argument exception because of service name mismatch + + try { + userTokenToSign2.signForAuthorizedService("weather.storage", "0", servicePrivateKeyStringK0); + fail(); + } catch (Exception ex) { + assertEquals(ex.getClass(), IllegalArgumentException.class); + } + } + + @Test + public void testValidateForAuthorizedServiceInvalidSignature() throws InterruptedException, CryptoException { + long issueTime = System.currentTimeMillis() / 1000; + // Create and sign token + List authorizedServices = new ArrayList<>(); + authorizedServices.add("coretech.storage"); + + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime) + .authorizedServices(authorizedServices).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // now let's sign the token for an authorized service + + userTokenToSign.signForAuthorizedService("coretech.storage", "1", servicePrivateKeyStringK1); + + // Create a token for validation using the signed data + + PrincipalToken userTokenToValidate = new PrincipalToken(userTokenToSign.getSignedToken()); + StringBuilder errMsg = new StringBuilder(); + assertTrue(userTokenToValidate.validateForAuthorizedService(servicePublicKeyStringK1, errMsg)); + + // now let's add a couple of extra characters to the signature + + String tamperedToken = userTokenToSign.getSignedToken(); + userTokenToValidate = new PrincipalToken(tamperedToken.replace(";bs=", ";bs=ab")); + errMsg = new StringBuilder(); + assertFalse(userTokenToValidate.validateForAuthorizedService(servicePublicKeyStringK1, errMsg)); + assertTrue(!errMsg.toString().isEmpty()); + } + + @Test + public void testValidateForAuthorizedServiceNoSignature() throws InterruptedException, CryptoException { + long issueTime = System.currentTimeMillis() / 1000; + // Create and sign token + List authorizedServices = new ArrayList<>(); + authorizedServices.add("coretech.storage"); + + PrincipalToken userTokenToSign = new PrincipalToken.Builder(usrVersion, usrDomain, usrName) + .salt(salt).issueTime(issueTime).expirationWindow(expirationTime) + .authorizedServices(authorizedServices).build(); + + userTokenToSign.sign(servicePrivateKeyStringK0); + + // Create a token for validation using the signed data without + // signing for authorized service so there won't be bs field. + + PrincipalToken userTokenToValidate = new PrincipalToken(userTokenToSign.getSignedToken()); + StringBuilder errMsg = new StringBuilder(); + assertFalse(userTokenToValidate.validateForAuthorizedService(servicePublicKeyStringK1, errMsg)); + assertTrue(!errMsg.toString().isEmpty()); + } + + @Test + public void testIsValidAuthorizedServiceTokenStandardToken() { + + // testing standard token + + String token = "v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;s=signature"; + PrincipalToken svcToken = new PrincipalToken(token); + StringBuilder errMsg = new StringBuilder(); + assertTrue(svcToken.isValidAuthorizedServiceToken(errMsg)); + } + + @Test + public void testIsValidAuthorizedServiceTokenValidSvcToken() { + + // testing valid service token - we have multiple cases + // to consider: + // single service entry in list + service name + + String token = "v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;b=svc1;s=signature;bk=0;bn=svc1;bs=signature"; + PrincipalToken svcToken = new PrincipalToken(token); + StringBuilder errMsg = new StringBuilder(); + assertTrue(svcToken.isValidAuthorizedServiceToken(errMsg)); + + // single service entry in list + no service name + + token = "v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;b=svc1;s=signature;bk=0;bs=signature"; + svcToken = new PrincipalToken(token); + assertTrue(svcToken.isValidAuthorizedServiceToken(null)); + + // multiple service entries in list + service name + + token = "v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;b=svc1,svc2;s=signature;bk=0;bn=svc1;bs=signature"; + svcToken = new PrincipalToken(token); + assertTrue(svcToken.isValidAuthorizedServiceToken(errMsg)); + } + + @Test + public void testIsValidAuthorizedServiceTokenSvcYesSigNo() { + + // we're going to test where we have an authorized service + // name but no corresponding signature + + String token = "v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;b=svc1;s=signature"; + PrincipalToken svcToken = new PrincipalToken(token); + StringBuilder errMsg = new StringBuilder(); + assertFalse(svcToken.isValidAuthorizedServiceToken(errMsg)); + assertTrue(!errMsg.toString().isEmpty()); + } + + @Test + public void testIsValidAuthorizedServiceTokenSvcNoSigYes() { + + // we're going to test where we have an authorized + // service signature but no service name + + String token = "v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;s=signature;bk=0;bs=signature"; + PrincipalToken svcToken = new PrincipalToken(token); + StringBuilder errMsg = new StringBuilder(); + assertFalse(svcToken.isValidAuthorizedServiceToken(errMsg)); + assertTrue(!errMsg.toString().isEmpty()); + } + + @Test + public void testIsValidAuthorizedServiceTokenUnknownSvc() { + + // we're going to test where we have both service name + // and signature but the service name is not in the + // service list (single entry) + + String token = "v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;b=svc1;s=signature;bk=0;bn=svc3;bs=signature"; + PrincipalToken svcToken = new PrincipalToken(token); + StringBuilder errMsg = new StringBuilder(); + assertFalse(svcToken.isValidAuthorizedServiceToken(errMsg)); + assertTrue(!errMsg.toString().isEmpty()); + + // service list (multiple entries) + + token = "v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;b=svc1,svc2;s=signature;bk=0;bn=svc3;bs=signature"; + svcToken = new PrincipalToken(token); + errMsg = new StringBuilder(); + assertFalse(svcToken.isValidAuthorizedServiceToken(errMsg)); + assertTrue(!errMsg.toString().isEmpty()); + } + + @Test + public void testIsValidAuthorizedServiceTokenNoSvcNotSingle() { + + // we're going to test where we have a service list + // and signature but no service name and our list + // contains multiple entries + + String token = "v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;b=svc1,svc2;s=signature;bk=0;bs=signature"; + PrincipalToken svcToken = new PrincipalToken(token); + StringBuilder errMsg = new StringBuilder(); + assertFalse(svcToken.isValidAuthorizedServiceToken(errMsg)); + assertTrue(!errMsg.toString().isEmpty()); + } + + @Test + public void testPrincipalTokenParserKeyService() { + + String token = "v=S1;d=domain;n=service;t=1234;e=1235;k=0;z=zms;o=athenz.ci.service;h=host1;i=1.2.3.4;s=signature"; + PrincipalToken svcToken = new PrincipalToken(token); + + assertEquals(svcToken.getKeyService(), "zms"); + assertEquals(svcToken.getOriginalRequestor(), "athenz.ci.service"); + } + + @Test + public void testPrincipalTokenGenerationKeyService() { + PrincipalToken token = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .issueTime(36000).expirationWindow(100).host("localhost").ip("127.0.0.1") + .salt("salt").keyId("zone1").keyService("zts").originalRequestor("athenz.ci.service").build(); + token.sign(servicePrivateKeyStringK0); + + String strToken = token.getSignedToken(); + int idx = strToken.indexOf(";z=zts;"); + assertTrue(idx != -1); + + idx = strToken.indexOf(";o=athenz.ci.service;"); + assertTrue(idx != -1); + } + + @Test + public void testValidateForAuthorizedServiceIlligal() { + PrincipalToken token = new PrincipalToken("bs=signature;v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;b=svc1;s=signature;bk=0;bn=svc1"); + assertFalse(token.validateForAuthorizedService(null, null)); + + token = new PrincipalToken("v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;b=svc1;s=signature;bk=1;bn=svc1;bs=signature"); + assertFalse(token.validateForAuthorizedService(null, null)); + + assertFalse(token.validateForAuthorizedService(servicePublicKeyStringK1, null)); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/RoleTokenTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/RoleTokenTest.java new file mode 100644 index 00000000000..3dffddf6f29 --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/RoleTokenTest.java @@ -0,0 +1,478 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.token; + +import static org.testng.Assert.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.util.CryptoException; + +public class RoleTokenTest { + + private final String rolVersion = "Z1"; + private final String svcDomain = "sports"; + private final String salt = "aAkjbbDMhnLX"; + private final String testKeyVersionK0 = "0"; + private final String testKeyVersionK1 = "1"; + + private final long expirationTime = 10; // 10 seconds + + private String ztsPublicKeyStringK0 = null; + private String ztsPrivateKeyStringK0 = null; + private String ztsPublicKeyStringK1 = null; + private String ztsPrivateKeyStringK1 = null; + + @BeforeTest + private void loadKeys() throws IOException { + + Path path = Paths.get("./src/test/resources/zts_public_k0.key"); + ztsPublicKeyStringK0 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/zts_public_k1.key"); + ztsPublicKeyStringK1 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/zts_private_k0.key"); + ztsPrivateKeyStringK0 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/zts_private_k1.key"); + ztsPrivateKeyStringK1 = new String(Files.readAllBytes(path)); + } + + private RoleToken createRoleTokenToValidate(List roles) + throws CryptoException { + // Create token and sign + RoleToken rollTokenToSign = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).expirationWindow(expirationTime).build(); + + rollTokenToSign.sign(ztsPrivateKeyStringK0); + + return new RoleToken(rollTokenToSign.getSignedToken()); + } + + private RoleToken createRoleTokenToValidate(List roles, String keyVersion) + throws CryptoException { + // Create token and sign + RoleToken rollTokenToSign = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).expirationWindow(expirationTime).keyId(keyVersion).build(); + + String privateKey = null; + if ("0".equals(keyVersion)) { + privateKey = ztsPrivateKeyStringK0; + } else if ("1".equals(keyVersion)) { + privateKey = ztsPrivateKeyStringK1; + } + + rollTokenToSign.sign(privateKey); + + return new RoleToken(rollTokenToSign.getSignedToken()); + } + + @Test + public void testRoleToken() throws InterruptedException, CryptoException { + // Add some roles + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + roles.add("fantasy.tenant.sports.admin"); + roles.add("fantasy.tenant.sports.reader"); + roles.add("fantasy.tenant.sports.writer"); + roles.add("fantasy.tenant.sports.scanner"); + + // Create a token for validation using the signed data + RoleToken rollTokenToValidate = createRoleTokenToValidate(roles, testKeyVersionK1); + assertNotNull(rollTokenToValidate.getSignedToken()); + + // Validate all input data + assertEquals(rollTokenToValidate.getVersion(), rolVersion); + assertEquals(rollTokenToValidate.getDomain(), svcDomain); + List rolesToValidate = rollTokenToValidate.getRoles(); + assertEquals(rolesToValidate.size(), roles.size()); + assertTrue(rolesToValidate.equals(roles)); + assertEquals(rollTokenToValidate.getKeyId(), testKeyVersionK1); + + // Validate the signature and that expiration time had not elapsed + assertTrue(rollTokenToValidate.validate(ztsPublicKeyStringK1, 300)); + + // Create ServiceToken with null keyVersion which should default to 0 + rollTokenToValidate = createRoleTokenToValidate(roles); + assertEquals(rollTokenToValidate.getKeyId(), "0"); + + // Validate the signature using key(k0) and that expiration time had not elapsed + assertTrue(rollTokenToValidate.validate(ztsPublicKeyStringK0, 300)); + } + + @Test + public void testRoleToken_Expired() throws InterruptedException, + CryptoException { + // Add some roles + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + roles.add("fantasy.tenant.sports.admin"); + roles.add("fantasy.tenant.sports.reader"); + roles.add("fantasy.tenant.sports.writer"); + roles.add("fantasy.tenant.sports.scanner"); + + // Create a token for validation using the signed data + RoleToken rollTokenToValidate = createRoleTokenToValidate(roles, testKeyVersionK0); + + // Let expiration time elapse + Thread.sleep((expirationTime + 10) * 1000); + + // Validate that the expiration time has elapsed + assertFalse(rollTokenToValidate.validate(ztsPublicKeyStringK0, 300)); + } + + @Test + public void testTokenStringConstructor() { + List roles = new ArrayList(); + roles.add("storage.tenant.activator.actionmap.w"); + RoleToken rToken = new RoleToken.Builder(rolVersion, svcDomain, roles) + .salt(salt).expirationWindow(expirationTime).build(); + + rToken.sign(ztsPrivateKeyStringK0); // now its signed + String signedToken = rToken.getSignedToken(); + RoleToken sigToken1 = new RoleToken(signedToken); + assertEquals(sigToken1.getSignedToken(), signedToken); + + String unsignedTok = rToken.getUnsignedToken(); + String signature = rToken.getSignature(); + String newSignedToken = unsignedTok + ";s=" + signature; + assertEquals(newSignedToken, signedToken); + + // instantiate a Token with the unsigned token + signature + RoleToken sigToken2 = new RoleToken(newSignedToken); + assertEquals(sigToken2.getSignedToken(), signedToken); + assertEquals(sigToken2.getUnsignedToken(), unsignedTok); + assertEquals(sigToken2.getSignature(), signature); + } + + @Test + public void testEmptyToken() { + + try { + @SuppressWarnings("unused") + RoleToken token = new RoleToken(""); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testNullToken() { + + try { + @SuppressWarnings("unused") + RoleToken token = new RoleToken(null); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testTokenWithoutSignature() { + + RoleToken token = new RoleToken("v=S1;d=coretech;r=role1,role2"); + assertEquals(token.getDomain(), "coretech"); + assertNotNull(token.getRoles()); + assertEquals(token.getVersion(), "S1"); + assertNull(token.getUnsignedToken()); + } + + @Test + public void testTokenInvalidVersionValue() { + + RoleToken token = new RoleToken("v=S1=S2;d=coretech;r=role1,role2"); + assertNull(token.getVersion()); + } + + @Test + public void testTokenDomainNull() { + + try { + @SuppressWarnings("unused") + RoleToken token = new RoleToken("v=S1;r=role1,role2;s=signature"); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testTokenDomainEmpty() { + + try { + @SuppressWarnings("unused") + RoleToken token = new RoleToken("v=S1;d=;r=role1,role2;s=signature"); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testTokenRolesNull() { + + try { + @SuppressWarnings("unused") + RoleToken token = new RoleToken("v=S1;d=coretech;s=signature"); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testTokenRolesEmpty() { + + try { + @SuppressWarnings("unused") + RoleToken token = new RoleToken("v=S1;d=coretech;r=;s=signature"); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderRequiredVersionNull() { + + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + try { + @SuppressWarnings("unused") + RoleToken.Builder builder = new RoleToken.Builder(null, svcDomain, roles); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderRequiredVersionEmptyString() { + + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + try { + @SuppressWarnings("unused") + RoleToken.Builder builder = new RoleToken.Builder("", svcDomain, roles); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderRequiredDomainNull() { + + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + try { + @SuppressWarnings("unused") + RoleToken.Builder builder = new RoleToken.Builder(rolVersion, null, roles); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderRequiredDomainEmptyString() { + + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + try { + @SuppressWarnings("unused") + RoleToken.Builder builder = new RoleToken.Builder(rolVersion, "", roles); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderRequiredRoleNull() { + + try { + @SuppressWarnings("unused") + RoleToken.Builder builder = new RoleToken.Builder(rolVersion, svcDomain, null); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderRequiredRoleEmptyString() { + + List roles = new ArrayList(); + try { + @SuppressWarnings("unused") + RoleToken.Builder builder = new RoleToken.Builder(rolVersion, svcDomain, roles); + fail(); + } catch (IllegalArgumentException ex) { + assertTrue(true); + } + } + + @Test + public void testBuilderDefaultOptionalValues() { + + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + + RoleToken token = new RoleToken.Builder(rolVersion, svcDomain, roles).build(); + assertEquals(token.getVersion(), rolVersion); + assertEquals(token.getDomain(), svcDomain); + assertEquals(token.getRoles(), roles); + assertNull(token.getHost()); + assertNotNull(token.getSalt()); + assertEquals(token.getKeyId(), "0"); + assertNull(token.getIP()); + long timestamp = token.getTimestamp(); + assertTrue(timestamp != 0); + assertEquals(token.getExpiryTime(), timestamp + 3600); + } + + @Test + public void testBuilderAllValues() { + + List roles = new ArrayList(); + roles.add("storage.tenant.weather.updater"); + + RoleToken token = new RoleToken.Builder(rolVersion, svcDomain, roles) + .issueTime(36000).expirationWindow(100).host("localhost").ip("127.0.0.1") + .salt("salt").keyId("zone1").principal("user.joe").build(); + + assertEquals(token.getVersion(), rolVersion); + assertEquals(token.getDomain(), svcDomain); + assertEquals(token.getRoles(), roles); + assertEquals(token.getHost(), "localhost"); + assertEquals(token.getSalt(), "salt"); + assertEquals(token.getKeyId(), "zone1"); + assertEquals(token.getIP(), "127.0.0.1"); + assertEquals(token.getPrincipal(), "user.joe"); + assertEquals(token.getTimestamp(), 36000); + assertEquals(token.getExpiryTime(), 36000 + 100); + } + + @Test + public void testRoleTokenWithPrincipal() { + + List roles = new ArrayList(); + roles.add("reader"); + + RoleToken token = new RoleToken.Builder(rolVersion, svcDomain, roles) + .issueTime(36000).expirationWindow(100).host("localhost").ip("127.0.0.1") + .salt("salt").keyId("zone1").principal("coretech.storage").build(); + token.sign(ztsPrivateKeyStringK0); + + assertTrue(token.getSignedToken().contains(";p=coretech.storage;")); + } + + @Test + public void testRoleTokenWithNullPrincipal() { + + List roles = new ArrayList(); + roles.add("reader"); + + RoleToken token = new RoleToken.Builder(rolVersion, svcDomain, roles) + .issueTime(36000).expirationWindow(100).host("localhost").ip("127.0.0.1") + .salt("salt").keyId("zone1").principal(null).build(); + token.sign(ztsPrivateKeyStringK0); + + assertFalse(token.getSignedToken().contains(";p=")); + } + + @Test + public void testRoleTokenWithEmptyPrincipal() { + + List roles = new ArrayList(); + roles.add("reader"); + + RoleToken token = new RoleToken.Builder(rolVersion, svcDomain, roles) + .issueTime(36000).expirationWindow(100).host("localhost").ip("127.0.0.1") + .salt("salt").keyId("zone1").principal("").build(); + token.sign(ztsPrivateKeyStringK0); + + assertFalse(token.getSignedToken().contains(";p=")); + } + + @Test + public void testRoleTokenWithoutPrincipal() { + + List roles = new ArrayList(); + roles.add("reader"); + + RoleToken token = new RoleToken.Builder(rolVersion, svcDomain, roles) + .issueTime(36000).expirationWindow(100).host("localhost").ip("127.0.0.1") + .salt("salt").keyId("zone1").build(); + token.sign(ztsPrivateKeyStringK0); + + assertFalse(token.getSignedToken().contains(";p=")); + } + + @Test + public void testRoleTokenWithProxyUser() { + + List roles = new ArrayList(); + roles.add("reader"); + + RoleToken token = new RoleToken.Builder(rolVersion, svcDomain, roles) + .issueTime(36000).expirationWindow(100).host("localhost").ip("127.0.0.1") + .salt("salt").keyId("zone1").principal("coretech.storage") + .proxyUser("user.user3").domainCompleteRoleSet(true).build(); + token.sign(ztsPrivateKeyStringK0); + + assertTrue(token.getSignedToken().contains(";proxy=user.user3;")); + assertTrue(token.getSignedToken().contains(";c=1")); + } + + @Test + public void testRoleTokenParseWithProxyUser() { + + RoleToken token = new RoleToken("v=S1;d=coretech;r=role1,role2;proxy=user.user3;c=1;s=signature"); + assertEquals(token.getDomain(), "coretech"); + assertEquals(token.getProxyUser(), "user.user3"); + assertTrue(token.getDomainCompleteRoleSet()); + } + + @Test + public void testRoleTokenParseWithoutProxyUser() { + + RoleToken token = new RoleToken("v=S1;d=coretech;r=role1,role2;s=signature"); + assertEquals(token.getDomain(), "coretech"); + assertNull(token.getProxyUser()); + assertFalse(token.getDomainCompleteRoleSet()); + } + + @Test + public void testRoleTokenHost() { + + RoleToken token = new RoleToken("v=S1;d=coretech;h=host;r=role1,role2;s=signature"); + assertEquals(token.getHost(), "host"); + assertNull(token.getProxyUser()); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/TokenTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/TokenTest.java new file mode 100644 index 00000000000..a957c12d29e --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/TokenTest.java @@ -0,0 +1,181 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.token; + +import static org.testng.Assert.*; + +import java.security.PublicKey; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.mockito.Mockito; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.util.CryptoException; + +public class TokenTest { + + private final String svcVersion = "S1"; + private final String svcDomain = "sports"; + private final String svcName = "fantasy"; + private final String host = "somehost.somecompany.com"; + private final String salt = "saltstring"; + private final String testKeyVersionK1 = "1"; + + private final long expirationTime = 10; // 10 seconds + + private String servicePrivateKeyStringK0 = null; + private String servicePrivateKeyStringK1 = null; + + @BeforeTest + private void loadKeys() throws IOException { + + Path path = Paths.get("./src/test/resources/fantasy_private_k0.key"); + servicePrivateKeyStringK0 = new String(Files.readAllBytes(path)); + + path = Paths.get("./src/test/resources/fantasy_private_k1.key"); + servicePrivateKeyStringK1 = new String(Files.readAllBytes(path)); + } + + @Test + public void testTokenValidateNullSignature() throws CryptoException { + PrincipalToken token = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .host(host).salt(salt).expirationWindow(expirationTime).build(); + + assertFalse(token.validate(servicePrivateKeyStringK0, 3600)); + + assertFalse(token.validate(servicePrivateKeyStringK0, 3600, null)); + + StringBuilder errMsg = new StringBuilder(); + assertFalse(token.validate(servicePrivateKeyStringK0, 3600, errMsg)); + assertTrue(!errMsg.toString().isEmpty()); + } + + @Test + public void testTokenValidateNullData() throws CryptoException { + + Token token = new Token(); + assertFalse(token.validate(servicePrivateKeyStringK0, 3600)); + } + + @Test + public void testTokenValidateFutureTimeStamp() throws CryptoException { + + long timestamp = System.currentTimeMillis() / 1000 + 4200; + PrincipalToken token = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .host(host).salt(salt).issueTime(timestamp).expirationWindow(expirationTime).build(); + token.sign(servicePrivateKeyStringK0); + + assertFalse(token.validate(servicePrivateKeyStringK0, 3600)); + + timestamp = System.currentTimeMillis() + 1000000; + token.setTimeStamp(timestamp,1000000); + PublicKey pubkey = Mockito.mock(PublicKey.class); + assertFalse(token.validate(pubkey, 3600,null)); + } + + @Test + public void testTokenValidateInvalidKey() throws CryptoException { + + PrincipalToken token = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .host(host).salt(salt).expirationWindow(expirationTime).build(); + token.sign(servicePrivateKeyStringK0); + + assertFalse(token.validate("InvalidPublicKey", 3600)); + + assertFalse(token.validate((PublicKey)null, 3600, null)); + } + + @Test + public void testTokenValidateNullKey() throws CryptoException { + + PrincipalToken token = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .host(host).salt(salt).expirationWindow(expirationTime).build(); + token.sign(servicePrivateKeyStringK0); + + assertFalse(token.validate(null, 3600)); + } + + @Test + public void testTokenGetters() throws InterruptedException, CryptoException { + + long timestamp = System.currentTimeMillis() / 1000; + PrincipalToken token = new PrincipalToken.Builder(svcVersion, svcDomain, svcName) + .host(host).ip("127.0.0.1").salt(salt).issueTime(timestamp) + .keyId(testKeyVersionK1).expirationWindow(expirationTime).build(); + token.sign(servicePrivateKeyStringK1); + + // Validate all input data + assertEquals(token.getVersion(), svcVersion); + assertEquals(token.getDomain(), svcDomain); + assertEquals(token.getName(), svcName); + assertEquals(token.getHost(), host); + assertEquals(token.getSalt(), salt); + assertEquals(token.getKeyId(), testKeyVersionK1); + assertEquals(token.getIP(), "127.0.0.1"); + assertEquals(token.getTimestamp(), timestamp); + assertEquals(token.getExpiryTime(), timestamp + expirationTime); + assertNotNull(token.getSignature()); + } + + @Test + public void testGetUnsignedTokenFromSignedToken() { + String [] signedTokens = { + // this is a RoleToken + "v=Z1;d=sports;r=storage.tenant.weather.updater;p=user.joe;a=aAkjbbDMhnLX;t=1447361682;e=1447361692;k=0;i=127.0.0.1;s=IKT3MhlfxMajh9KqvNFhuJwyHQB8M9qVmgok389wmcRZ_kqMKaf72sC3.u0Qh4VlWk.DReX8y17V.qV0wnGwNPwfUBKG9SR88SL_MBvSaVHst9wQN20v.gCzFf8IXzehEFID5tjCIFAmaLEn71bCS4oKMiPEx4FtP4OdYdeL_d4", + "v=Z1;d=sports;r=storage.tenant.weather.updater,fantasy.tenant.sports.admin,fantasy.tenant.sports.reader,fantasy.tenant.sports.writer,fantasy.tenant.sports.scanner;p=coretech.storage;a=aAkjbbDMhnLX;t=1447361682;e=1447361692;k=0;i=127.0.0.1;s=C68KMYxivWqTTg8YWGc1aXUkgbgHOQDH8iRePZWY9aLUidkEQSvBkveRFi4Sap6q800Qt1GVnF6aN1OMk6YNIc_0E_xdRj9LJriS6Qq6ss79y76J_OSGSIwBXNeDWP6fq1SPW_MlUiXPE3TJojG_W8C0lwtWRppP0UZGAjzs4bc-", + // this is a PrincipalToken + "v=S1;d=cd.project;n=nfl;h=somehost.somecompany.com;a=saltvalue;t=1447361732;e=1447361742;s=NlISFQqXz1ji8zdVdGKjKZHJBloo11S.tXLo6t.GmnCt9S6c8AATzzZ2XdMeRlX2b0ykRiS0yjmrXg.grMPin3cHiB_FdLL05.w29OUBgxJr71.11_09iOoqy0ivGqyXoSO2GQbtXJfQeJ6HFHWPef1xyNV0Fswd8e6HQtyxGLA-", + "v=S1;d=cd;n=12345;h=somehost.somecompany.com;a=saltvalue;t=1447361732;e=1447361742;k=0;s=lg5JMqJb9Hd5Vd12VdC2d.Pu0TSlynGtulV7GjT9RQsSFaTsvabLPIehNT7iJczVq_POzWTA7HYRe7ZNGfGTe6P26C_qECX._ylbYVznLnZSFW3IQFMMPc2yjiE_twFgXAVtT1sWHjcf8zxK4RVij_8vziTiUrqU_ExioO019XE-", + }; + + for (String signedToken: signedTokens) { + String unsignedToken = Token.getUnsignedToken(signedToken); + assertFalse(unsignedToken == signedToken); + assertTrue(unsignedToken.length() < signedToken.length()); + assertTrue(signedToken.startsWith(unsignedToken)); + } + } + + @Test + public void testGetUnsignedTokenFromUnsignedToken() { + String [] signedTokens = { + // this is a RoleToken + "v=Z1;d=sports;r=storage.tenant.weather.updater;p=user.joe;a=aAkjbbDMhnLX;t=1447361682;e=1447361692;k=0;i=127.0.0.1", + "v=Z1;d=sports;r=storage.tenant.weather.updater,fantasy.tenant.sports.admin,fantasy.tenant.sports.reader,fantasy.tenant.sports.writer,fantasy.tenant.sports.scanner;p=coretech.storage;a=aAkjbbDMhnLX;t=1447361682;e=1447361692;k=0;i=127.0.0.1", + // this is a PrincipalToken + "v=S1;d=cd.project;n=nfl;h=somehost.somecompany.com;a=saltvalue;t=1447361732;e=1447361742", + "v=S1;d=cd;n=12345;h=somehost.somecompany.com;a=saltvalue;t=1447361732;e=1447361742;k=0", + }; + + for (String signedToken: signedTokens) { + String unsignedToken = Token.getUnsignedToken(signedToken); + assertTrue(unsignedToken == signedToken); + } + } + + @Test + public void testValidateFail() { + Token token = new Token(); + + PublicKey pubkey = null; + + assertFalse(token.validate(pubkey, 3600,null)); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/CryptoTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/CryptoTest.java new file mode 100644 index 00000000000..fafbd9f2a52 --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/CryptoTest.java @@ -0,0 +1,516 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.Date; + +import static org.testng.Assert.*; + +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.auth.util.CryptoException; +import com.yahoo.rdl.Array; +import com.yahoo.rdl.Struct; + +public class CryptoTest { + + final File rsaPrivateKey = new File("./src/test/resources/rsa_private.key"); + final File rsaPublicKey = new File("./src/test/resources/rsa_public.key"); + final File rsaPublicX590Cert = new File("./src/test/resources/rsa_public_x509.cert"); + final File rsaPublicInvalidKey = new File("./src/test/resources/rsa_public_invalid.key"); + + final File ecPrivateKey = new File("./src/test/resources/ec_private.key"); + final File ecPublicKey = new File("./src/test/resources/ec_public.key"); + final File ecPublicX509Cert = new File("./src/test/resources/ec_public_x509.cert"); + final File ecPublicInvalidKey = new File("./src/test/resources/ec_public_invalid.key"); + final File ecPrivateParamPrime256v1Key = new File("./src/test/resources/ec_private_param_prime256v1.key"); + final File ecPublicParamPrime256v1Key = new File("./src/test/resources/ec_public_param_prime256v1.key"); + final File ecPrivateParamSecp384r1Key = new File("./src/test/resources/ec_private_param_secp384r1.key"); + final File ecPublicParamSecp384r1Key = new File("./src/test/resources/ec_public_param_secp384r1.key"); + final File ecPrivateParamsKey = new File("./src/test/resources/ec_private_params.key"); + final File ecPublicParamsKey = new File("./src/test/resources/ec_public_params.key"); + final File argFile = new File("./src/test/resources/arg_file"); + + final File privateEncryptedKey = new File("./src/test/resources/private_encrypted.key"); + final String encryptedKeyPassword = "athenz"; + + final String serviceToken = "v=S1;d=coretech;n=storage;t=1234567000;e=123456800;h=localhost"; + final String serviceRSASignature = "VsUlcNozK4as1FjPbowEE_DFDD8KWpQzphadfbt_TsMoCTLFpYrMzKTu_nHKemJmEi0bbPwj7hRLrIKEFu2VjQ--"; + final String serviceECSignature = "MEQCIEBnyNCxp5GSeua3K9OenyetmVs4F68VB.Md1JRaU4OXAiBWAxlJLe74ZV4QDqapsD4FJm.MA3mv0FMcq.LEevJa0g--"; + + @Test + public void testSignVerifyRSAKey() { + + PrivateKey privateKey = Crypto.loadPrivateKey(rsaPrivateKey); + assertNotNull(privateKey); + + String signature = Crypto.sign(serviceToken, privateKey); + assertEquals(signature, serviceRSASignature); + + PublicKey publicKey = Crypto.loadPublicKey(rsaPublicKey); + assertNotNull(publicKey); + + assertTrue(Crypto.verify(serviceToken, publicKey, signature)); + } + + @Test + public void testSignVerifyRSAKey_Invalid() { + + PublicKey publicKey = Crypto.loadPublicKey(rsaPublicInvalidKey); + assertNotNull(publicKey); + + assertFalse(Crypto.verify(serviceToken, publicKey, serviceRSASignature)); + } + + @Test + public void testSignVerifyRSAKey_X509() { + + PublicKey publicKey = Crypto.loadPublicKey(rsaPublicX590Cert); + assertNotNull(publicKey); + + assertTrue(Crypto.verify(serviceToken, publicKey, serviceRSASignature)); + } + + @Test + public void testSignVerifyECKey() { + + PrivateKey privateKey = Crypto.loadPrivateKey(ecPrivateKey); + assertNotNull(privateKey); + + String signature = Crypto.sign(serviceToken, privateKey); + + PublicKey publicKey = Crypto.loadPublicKey(ecPublicKey); + assertNotNull(publicKey); + + assertTrue(Crypto.verify(serviceToken, publicKey, signature)); + } + + @Test + public void testSignVerifyECKey_Invalid() { + + PublicKey publicKey = Crypto.loadPublicKey(ecPublicInvalidKey); + assertNotNull(publicKey); + + assertFalse(Crypto.verify(serviceToken, publicKey, serviceECSignature)); + } + + @Test + public void testSignVerifyECKey_X509() { + + PublicKey publicKey = Crypto.loadPublicKey(ecPublicX509Cert); + assertNotNull(publicKey); + + assertTrue(Crypto.verify(serviceToken, publicKey, serviceECSignature)); + } + + @Test + public void testLoadX509CertificateFile() { + + X509Certificate cert = Crypto.loadX509Certificate(ecPublicX509Cert); + assertNotNull(cert); + + assertEquals(cert.getSubjectX500Principal().getName(), + "CN=athenz.syncer,O=My Test Company,L=Sunnyvale,ST=CA,C=US"); + } + + @Test + public void testLoadX509CertificateString() throws IOException { + + Path path = Paths.get("src/test/resources/valid_cn_x509.cert"); + String certStr = new String(Files.readAllBytes(path)); + X509Certificate cert = Crypto.loadX509Certificate(certStr); + assertNotNull(cert); + + assertEquals(cert.getSubjectX500Principal().getName(), + "CN=athenz.syncer,O=My Test Company,L=Sunnyvale,ST=CA,C=US"); + } + + @Test + public void testLoadX509CertificateInvalid() throws IOException { + + Path path = Paths.get("src/test/resources/invalid_x509.cert"); + String certStr = new String(Files.readAllBytes(path)); + try { + Crypto.loadX509Certificate(certStr); + fail(); + } catch (CryptoException ex) { + assertTrue(true, "Caught expected CryptoException"); + } + } + + @Test + public void testLoadPrivateEncryptedKey() { + PrivateKey privateKey = Crypto.loadPrivateKey(privateEncryptedKey, encryptedKeyPassword); + assertNotNull(privateKey); + } + + @Test + public void testLoadPrivateEncryptedKeyInvalidPassword() { + + // first try with no password + + try { + Crypto.loadPrivateKey(privateEncryptedKey, null); + fail(); + } catch (CryptoException ex) { + assertTrue(ex.getMessage().contains("No password specified")); + } + + // now let's try with invalid password + + try { + Crypto.loadPrivateKey(privateEncryptedKey, "InvalidPassword"); + fail(); + } catch (CryptoException ex) { + assertTrue(true, "Invalid password specified"); + } + } + + @Test + public void testSignVerifyECParamPrime256v1Key() { + + PrivateKey privateKey = Crypto.loadPrivateKey(ecPrivateParamPrime256v1Key); + assertNotNull(privateKey); + + String signature = Crypto.sign(serviceToken, privateKey); + + PublicKey publicKey = Crypto.loadPublicKey(ecPublicParamPrime256v1Key); + assertNotNull(publicKey); + + assertTrue(Crypto.verify(serviceToken, publicKey, signature)); + } + + @Test + public void testSignVerifyECParamsKey() { + + PrivateKey privateKey = Crypto.loadPrivateKey(ecPrivateParamsKey); + assertNotNull(privateKey); + + String signature = Crypto.sign(serviceToken, privateKey); + + PublicKey publicKey = Crypto.loadPublicKey(ecPublicParamsKey); + assertNotNull(publicKey); + + assertTrue(Crypto.verify(serviceToken, publicKey, signature)); + } + + @Test + public void testSignVerifyECParamSecp384r1Key() { + + PrivateKey privateKey = Crypto.loadPrivateKey(ecPrivateParamSecp384r1Key); + assertNotNull(privateKey); + + String signature = Crypto.sign(serviceToken, privateKey); + + PublicKey publicKey = Crypto.loadPublicKey(ecPublicParamSecp384r1Key); + assertNotNull(publicKey); + + assertTrue(Crypto.verify(serviceToken, publicKey, signature)); + } + + @Test + public void testSignVerifyECParamMixCurvesFail() { + + PrivateKey privateKey = Crypto.loadPrivateKey(ecPrivateParamPrime256v1Key); + assertNotNull(privateKey); + + String signature = Crypto.sign(serviceToken, privateKey); + + PublicKey publicKey = Crypto.loadPublicKey(ecPublicParamSecp384r1Key); + assertNotNull(publicKey); + + assertFalse(Crypto.verify(serviceToken, publicKey, signature)); + } + + @Test + public void testSignVerifyECParamKeyOpenssl() { + + PublicKey publicKey = Crypto.loadPublicKey(ecPublicParamPrime256v1Key); + assertNotNull(publicKey); + + // this test case is from ysecure using openssl + + String plainText = "This is a test of the ysecure public key interface. This is only a test."; + String signature = "MEUCIBjTLIhH_Rc3fkRXJ8CvzSqkIwxXqReg7nOe_q1t_C73AiEAky4NAP.CwlYKXlto93f_JTYOQqDpZSJeTYSe80fQ5vY-"; + + assertTrue(Crypto.verify(plainText, publicKey, signature)); + } + + @Test + public void testGetSignatureAlgorithmRSA() { + try { + assertEquals(Crypto.getSignatureAlgorithm("RSA"), "SHA256withRSA"); + assertEquals(Crypto.getSignatureAlgorithm("RSA", "SHA256"), "SHA256withRSA"); + assertEquals(Crypto.getSignatureAlgorithm("RSA", "SHA1"), "SHA1withRSA"); + } catch (NoSuchAlgorithmException e) { + fail(); + } + } + + @Test + public void testGetSignatureAlgorithmEC() { + try { + assertEquals(Crypto.getSignatureAlgorithm("ECDSA"), "SHA256withECDSA"); + assertEquals(Crypto.getSignatureAlgorithm("ECDSA", "SHA256"), "SHA256withECDSA"); + assertEquals(Crypto.getSignatureAlgorithm("ECDSA", "SHA1"), "SHA1withECDSA"); + } catch (NoSuchAlgorithmException e) { + fail(); + } + } + + @Test + public void testGetSignatureAlgorithmUnknown() { + + try { + assertEquals(Crypto.getSignatureAlgorithm("DSA", "SHA256"), "SHA256withDSA"); + assertEquals(Crypto.getSignatureAlgorithm("RSA", "SHA555"), "SHA555withRSA"); + assertEquals(Crypto.getSignatureAlgorithm("ECDSA", "SHA999"), "SHA999withECDSA"); + fail(); + } catch (NoSuchAlgorithmException e) { + assertTrue(true); + } + } + + @Test + public void testGetPKCS10CertRequest() throws IOException { + + Path path = Paths.get("src/test/resources/valid.csr"); + String certStr = new String(Files.readAllBytes(path)); + + PKCS10CertificationRequest req = Crypto.getPKCS10CertRequest(certStr); + assertNotNull(req); + assertEquals(req.getSubject().toString(), "C=US,ST=CA,L=Sunnyvale,O=My Test Company,CN=athenz.syncer"); + } + + @Test + public void testGetPKCS10CertRequestInvalid() throws IOException { + + // first try with empty values + + try { + Crypto.getPKCS10CertRequest(null); + fail(); + } catch (CryptoException ex) { + assertTrue(ex.getMessage().contains("CSR is null")); + } + + try { + Crypto.getPKCS10CertRequest(""); + fail(); + } catch (CryptoException ex) { + assertTrue(ex.getMessage().contains("CSR is null")); + } + + // now let's try with invalid csr + + Path path = Paths.get("src/test/resources/invalid.csr"); + String certStr = new String(Files.readAllBytes(path)); + + try { + Crypto.getPKCS10CertRequest(certStr); + fail(); + } catch (CryptoException ex) { + assertTrue(true, "Caught expected crypto exception"); + } + } + + @Test + public void testGenerateX509Certificate() throws IOException { + + Path path = Paths.get("src/test/resources/valid.csr"); + String certStr = new String(Files.readAllBytes(path)); + + PKCS10CertificationRequest certReq = Crypto.getPKCS10CertRequest(certStr); + X509Certificate caCertificate = Crypto.loadX509Certificate(ecPublicX509Cert); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(privateEncryptedKey, encryptedKeyPassword); + + X509Certificate cert = Crypto.generateX509Certificate(certReq, caPrivateKey, + caCertificate, 600, false); + assertNotNull(cert); + assertEquals(cert.getIssuerX500Principal().getName(), + "CN=athenz.syncer,O=My Test Company,L=Sunnyvale,ST=CA,C=US"); + + Date notAfter = cert.getNotAfter(); + long diff = notAfter.getTime() - System.currentTimeMillis(); + assertTrue(diff <= 600 * 60 * 1000); // convert minutes to milliseconds + + System.out.println("****** Generated Certificate *********"); + System.out.println(cert.toString()); + } + + @Test + public void testGenerateX509CertificateAltNames() throws IOException { + + Path path = Paths.get("src/test/resources/csr_altnames.csr"); + String certStr = new String(Files.readAllBytes(path)); + + PKCS10CertificationRequest certReq = Crypto.getPKCS10CertRequest(certStr); + X509Certificate caCertificate = Crypto.loadX509Certificate(ecPublicX509Cert); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(privateEncryptedKey, encryptedKeyPassword); + + X509Certificate cert = Crypto.generateX509Certificate(certReq, caPrivateKey, + caCertificate, 600, true); + assertNotNull(cert); + + System.out.println("****** Generated Certificate With Alternative Names *********"); + System.out.println(cert.toString()); + System.out.println("PEM format:"); + System.out.println(Crypto.x509CertificateToPem(cert)); + } + + @Test + public void testGenerateX509CertificateReqPrivateKey() throws IOException { + + Path path = Paths.get("src/test/resources/valid.csr"); + String certStr = new String(Files.readAllBytes(path)); + + PKCS10CertificationRequest certReq = Crypto.getPKCS10CertRequest(certStr); + X509Certificate caCertificate = Crypto.loadX509Certificate(ecPublicX509Cert); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(rsaPrivateKey); + + X509Certificate cert = Crypto.generateX509Certificate(certReq, caPrivateKey, + caCertificate, 600, false); + assertNotNull(cert); + assertEquals(cert.getIssuerX500Principal().getName(), + "CN=athenz.syncer,O=My Test Company,L=Sunnyvale,ST=CA,C=US"); + } + + @Test + public void testGenerateX509CertificateInvalid() throws IOException { + + Path path = Paths.get("src/test/resources/valid.csr"); + String certStr = new String(Files.readAllBytes(path)); + + PKCS10CertificationRequest certReq = Crypto.getPKCS10CertRequest(certStr); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(rsaPrivateKey); + + try { + Crypto.generateX509Certificate(certReq, caPrivateKey, null, 600, true); + fail(); + } catch (CryptoException ex) { + assertTrue(true, "Caught excepted exception"); + } + } + + @Test + public void testX509CertificateToPem() { + X509Certificate cert = Crypto.loadX509Certificate(ecPublicX509Cert); + String pem = Crypto.x509CertificateToPem(cert); + assertNotNull(pem); + assertTrue(pem.contains("BEGIN CERTIFICATE"), pem); + assertTrue(pem.contains("END CERTIFICATE"), pem); + } + + @Test + public void testLoadReaderPrivateKey() throws IOException{ + try (java.io.FileReader fileReader = new java.io.FileReader(rsaPrivateKey)) { + PrivateKey privateKey = Crypto.loadPrivateKey(fileReader); + assertNotNull(privateKey); + } catch (IOException e) { + fail(); + } + } + + @Test + public void testEnDecodedFile(){ + String encoded = Crypto.encodedFile(argFile); + assertNotNull(encoded); + + String decoded = Crypto.ybase64DecodeString(encoded); + assertEquals(decoded, "check\n"); + } + + @Test + public void testEncodedFileStream() throws Exception { + try (FileInputStream in = new FileInputStream(argFile)) { + String encoded = Crypto.encodedFile(in); + assertNotNull(encoded); + + String decoded = Crypto.ybase64DecodeString(encoded); + assertEquals(decoded, "check"); + } catch (Exception e){ + fail(); + } + } + + @Test + public void testSHA256() { + byte [] checkByte = Crypto.sha256("check"); + assertNotNull(checkByte); + } + + @Test + public void testHmac() throws Exception { + Struct argData = new Struct(3); + argData.append("00", 0); + argData.append("01", 1); + argData.append("02", 2); + + String hmac = Crypto.hmac(argData, "check"); + + assertNotNull(hmac); + + boolean checkStringNum = false; + if(hmac.length() == 44){ + checkStringNum = true; + } + assertTrue(checkStringNum); + } + + @Test + public void testSign(){ + Struct argData = new Struct(3); + argData.append("00", 0); + argData.append("01", 1); + argData.append("02", 2); + + PrivateKey privateKey = Crypto.loadPrivateKey(rsaPrivateKey); + String checkSign = Crypto.sign(argData,privateKey); + + assertNotNull(checkSign); + assertEquals(checkSign.length(),88); + } + + @Test + public void testCanonical() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Class c = Crypto.class; + Crypto check = new Crypto(); + + Method m = c.getDeclaredMethod("canonical", Object.class ); + m.setAccessible(true); + Array p = null; + Object a = (Object) m.invoke(check, p); + assertNull(a); + Array az = new Array(); + az.add("aa"); + a = (Object) m.invoke(check, az); + assertNotNull(a); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/ValidateTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/ValidateTest.java new file mode 100644 index 00000000000..22eb14075ff --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/ValidateTest.java @@ -0,0 +1,78 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.util; + +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.util.Validate; + +public class ValidateTest { + + @Test + public void testPrincipalNameValidationInvalid() { + + assertFalse(Validate.principalName("user:john%doe")); + assertFalse(Validate.principalName("user.user:john.doe.")); + assertFalse(Validate.principalName("user.user.:john.doe")); + assertFalse(Validate.principalName(".user:doe")); + assertFalse(Validate.principalName(".doe")); + assertFalse(Validate.principalName(":doe")); + assertFalse(Validate.principalName("doe:")); + assertFalse(Validate.principalName("::doe")); + assertFalse(Validate.principalName("doe::")); + assertFalse(Validate.principalName("user:john:doe")); + } + + @Test + public void testPrincipalNameValidationValid() { + + assertTrue(Validate.principalName("user:doe")); + assertTrue(Validate.principalName("user:doe")); + assertTrue(Validate.principalName("user:john.doe")); + assertTrue(Validate.principalName("user.user:doe")); + assertTrue(Validate.principalName("user.user:john.doe")); + assertTrue(Validate.principalName("user:john_doe")); + assertTrue(Validate.principalName("john-doe")); + assertTrue(Validate.principalName("user:john-doe")); + } + + @Test + public void testDomainNameValidationInvalid() { + + assertFalse(Validate.domainName("domain$sub")); + assertFalse(Validate.domainName("coretech:domain")); + assertFalse(Validate.domainName("55")); + assertFalse(Validate.domainName("3com.gov")); + } + + @Test + public void testDomainNameValidationValid() { + + assertTrue(Validate.domainName("domain")); + assertTrue(Validate.domainName("domain.sub.sub")); + assertTrue(Validate.domainName("domain_")); + assertTrue(Validate.domainName("_")); + assertTrue(Validate.domainName("_test._")); + assertTrue(Validate.domainName("sub1_sub2")); + assertTrue(Validate.domainName("sub1_sub2_sub3")); + assertTrue(Validate.domainName("sub1_sub2.sub3_sub4")); + assertTrue(Validate.domainName("sub1_sub2_.sub3_sub4_")); + assertTrue(Validate.domainName("sub1_sub2_.sub3_sub4_-")); + assertTrue(Validate.domainName("domain-part")); + assertTrue(Validate.domainName("com-test.gov")); + } +} diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/YBase64Test.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/YBase64Test.java new file mode 100644 index 00000000000..d62117535b8 --- /dev/null +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/util/YBase64Test.java @@ -0,0 +1,160 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.auth.util; + +import static org.testng.Assert.*; + +import java.nio.charset.StandardCharsets; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.util.CryptoException; +import com.yahoo.athenz.auth.util.YBase64; + +public class YBase64Test { + + final static String DOUBLE_PADDING = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRW" + + "UlLb1pJemowREFRY0RRZ0FFeSszVEJJL281SzVwUFpQS2RYdk5YSmQ2L1hYYwpoMmNUQTgyRlVlcUVFU2QxUy9nTj" + + "IrY0daRnhZOWNJYlRCL01vbDFueU9uOHFGQmpkS1JnSUM5MDlnPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg--"; + final static String NO_PADDING = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRW" + + "UlLb1pJemowREFRY0RRZ0FFa2Y5UzN3Q09tQ1BvbklQWTdGZHNHU05WQlAxOQorSlBMV2dST2hOV0pOMW1qZnNLa" + + "GJvZXZjNHNxeGdlb2xQaERCLzExeVFWSVdpcFlGanlYdFJVT0pnPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t"; + final static String SINGLE_PADDING = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRU" + + "JCUUFEU3dBd1NBSkJBT0lRMlY1NURmQk93VjNBMTZ1andOcStKcCtMTURrNwpKUXZldThMT3J5R1pWc25aQmxFVit" + + "za05FYTJzNzBHNmM4blBoRVJyZVBtYUQ2cjd5Wk50MGVVQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + + final static String SINGLE_PAD_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" + + "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAOIQ2V55DfBOwV3A16ujwNq+Jp+LMDk7\n" + + "JQveu8LOryGZVsnZBlEV+skNEa2s70G6c8nPhERrePmaD6r7yZNt0eUCAwEAAQ==\n" + + "-----END PUBLIC KEY-----\n"; + final static String DOUBLE_PAD_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" + + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy+3TBI/o5K5pPZPKdXvNXJd6/XXc\n" + + "h2cTA82FUeqEESd1S/gN2+cGZFxY9cIbTB/Mol1nyOn8qFBjdKRgIC909g==\n" + + "-----END PUBLIC KEY-----\n"; + final static String NO_PAD_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" + + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkf9S3wCOmCPonIPY7FdsGSNVBP19\n" + + "+JPLWgROhNWJN1mjfsKhboevc4sqxgeolPhDB/11yQVIWipYFjyXtRUOJg==\n" + + "-----END PUBLIC KEY-----"; + + @Test + public void testDecodeSinglePaddingCharacter() { + byte[] data = YBase64.decode(SINGLE_PADDING.getBytes(StandardCharsets.UTF_8)); + assertEquals(new String(data, StandardCharsets.UTF_8), SINGLE_PAD_PUBLIC_KEY); + } + + @Test + public void testDecodeDoublePaddingCharacter() { + byte[] data = YBase64.decode(DOUBLE_PADDING.getBytes(StandardCharsets.UTF_8)); + assertEquals(new String(data, StandardCharsets.UTF_8), DOUBLE_PAD_PUBLIC_KEY); + } + + @Test + public void testDecodeNoPaddingCharacter() { + byte[] data = YBase64.decode(NO_PADDING.getBytes(StandardCharsets.UTF_8)); + assertEquals(new String(data, StandardCharsets.UTF_8), NO_PAD_PUBLIC_KEY); + } + + @Test + public void testEncodeSinglePaddingCharacter() { + byte[] data = YBase64.encode(SINGLE_PAD_PUBLIC_KEY.getBytes(StandardCharsets.UTF_8)); + assertEquals(new String(data, StandardCharsets.UTF_8), SINGLE_PADDING); + } + + @Test + public void testEncodeDoublePaddingCharacter() { + byte[] data = YBase64.encode(DOUBLE_PAD_PUBLIC_KEY.getBytes(StandardCharsets.UTF_8)); + assertEquals(new String(data, StandardCharsets.UTF_8), DOUBLE_PADDING); + } + + @Test + public void testEncodeNoPaddingCharacter() { + byte[] data = YBase64.encode(NO_PAD_PUBLIC_KEY.getBytes(StandardCharsets.UTF_8)); + assertEquals(new String(data, StandardCharsets.UTF_8), NO_PADDING); + } + + @Test + public void testDecodeInvalidData() { + try { + YBase64.decode(null); + fail(); + } catch (NullPointerException ex) { + } + + try { + YBase64.decode("abcde\0".getBytes()); + fail(); + } catch (CryptoException ex) { + } + + try { + YBase64.decode("a-aa".getBytes()); + fail(); + } catch (CryptoException ex) { + } + + try { + byte[] a = new byte[] {(byte)0xff,(byte)0x97,(byte)0x97,(byte)0x97}; + YBase64.decode(a); + fail(); + } catch (CryptoException ex) { + } + + try { + byte[] b = new byte[] {(byte)0x97,(byte)0xff,(byte)0x97,(byte)0x97}; + YBase64.decode(b); + fail(); + } catch (CryptoException ex) { + } + + try { + byte[] c = new byte[] {(byte)0x97,(byte)0x97,(byte)0xff,(byte)0x97}; + YBase64.decode(c); + fail(); + } catch (CryptoException ex) { + } + + try { + byte[] d = new byte[] {(byte)0x97,(byte)0x97,(byte)0x97,(byte)0xff}; + YBase64.decode(d); + fail(); + } catch (CryptoException ex) { + } + + try { + YBase64.decode("aaa-".getBytes()); + fail(); + } catch (CryptoException ex) { + } + + try { + YBase64.decode("aa--".getBytes()); + fail(); + } catch (CryptoException ex) { + } + } + + @Test + public void testEncodeInvalidData() { + try { + YBase64.encode(null); + fail(); + } catch (NullPointerException ex) { + } + + byte[] data = new byte[0]; + assertNotNull(new String(YBase64.encode(data), StandardCharsets.UTF_8)); + } +} diff --git a/libs/java/auth_core/src/test/resources/arg_file b/libs/java/auth_core/src/test/resources/arg_file new file mode 100644 index 00000000000..f03fc12c17c --- /dev/null +++ b/libs/java/auth_core/src/test/resources/arg_file @@ -0,0 +1 @@ +check diff --git a/libs/java/auth_core/src/test/resources/aws_public.crt b/libs/java/auth_core/src/test/resources/aws_public.crt new file mode 100644 index 00000000000..7f8173633c7 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/aws_public.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw +FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD +VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z +ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u +IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl +cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e +ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3 +VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P +hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j +k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U +hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF +lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf +MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW +MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw +vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw +7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K + -----END CERTIFICATE----- diff --git a/libs/java/auth_core/src/test/resources/csr_altnames.csr b/libs/java/auth_core/src/test/resources/csr_altnames.csr new file mode 100644 index 00000000000..da16e91f561 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/csr_altnames.csr @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDEzCCAfsCAQAwUzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1OMRQwEgYDVQQH +EwtNaW5uZWFwb2xpczEhMB8GA1UECxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVk +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs8yLsdIRsMko/PS/M1QW +/Z4cqhmQ5k/hySCYCCzVYHaqBJScuNwE0lN9gmkIPhGBXwmsrliYGlb6f8vv1rhy +tV8dPrmJBrhQAQPfIMtMBJgNN3HfusrCzDvYDSErirGxWFDOOGPunpreKwrv/5m5 +PmlOtCRY/4qyXdu+ZLCSMjRXHGrIPQp4OwvopfKT8Iqsr0jj/R9I7kMtF2ckh3kB +t80j+hG0zQIJi7ksLUdE02n9zcgdVLfXD1kwtZi5A3UL50mGVKHmDHwnWxkkcjYn +o8eN7puuku7soQSx8cso4aghG2s0Stq/0pPa5smOkKOHuVdrAcmlO9DbR4sGo9hk +/wIDAQABoHsweQYJKoZIhvcNAQkOMWwwajAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF +4DBQBgNVHREESTBHgg5rYi5leGFtcGxlLmNvbYIUaGVscGRlc2suZXhhbXBsZS5v +cmeCE3N5c3RlbXMuZXhhbXBsZS5uZXSHBMCoAQGHBMCoRQ4wDQYJKoZIhvcNAQEF +BQADggEBAIPpnW59t0YnGMEZY1kbYJiHQ/9vl1DQyfbfGfLdLN9BKq4dVYBEyYJf +IIUZ1sS3LQ35D7RQ/ZK8SBlWnVhw0kitCq1S9L8I7l3E0FhmmlbXBUo55irEo5sL +fhpF92WNGqgLItPlmIQGckOhBqgV0b/ItuCutv8Tyc6IQrexOKOeCxG/T5mfvMr9 +QjZTasasqBpqj3eXZtnd14HAreUVCWLykljBLVi0C+d6YTDmWrOg2WT1TpH8Ax0B +hyP/ANt9TiMZkkxTgTcK/NswehxLFc+kK17mHybAQqEzK4TzPcWY9qd7meYOzuIW +xIA2JaOyehPaG95J4k8zVdFpJ+Rd6FU= +-----END CERTIFICATE REQUEST----- diff --git a/libs/java/auth_core/src/test/resources/ec_private.key b/libs/java/auth_core/src/test/resources/ec_private.key new file mode 100644 index 00000000000..3bd4d875a40 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/ec_private.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEICPnaShdecLr05bWB6JpkN9FsQURwsjnFfDf6NUpj9WDoAoGCCqGSM49 +AwEHoUQDQgAEjTHrARSTlPSGyVpPzcM1XLmv3xecbscCNDKeMKtx0J4BN1XZ5ul5 ++oGWL9JdnC8vf7s6IPcxOvIZtH7NFIVn+w== +-----END EC PRIVATE KEY----- diff --git a/libs/java/auth_core/src/test/resources/ec_private_param_prime256v1.key b/libs/java/auth_core/src/test/resources/ec_private_param_prime256v1.key new file mode 100644 index 00000000000..2cbd7451c89 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/ec_private_param_prime256v1.key @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIOVUPQXm03iv8HSPrhhTvo3VDbcsow8k6t/SAz0U3xkAoAoGCCqGSM49 +AwEHoUQDQgAE30K6pyN44ELKYoSFPlxeWMBpoqcZ092OKhHBXOOUBo9uSmMwHq6Z +wXkHZJclrPCbK3Y4Qxf/l/YW+v0T56A8vQ== +-----END EC PRIVATE KEY----- diff --git a/libs/java/auth_core/src/test/resources/ec_private_param_secp384r1.key b/libs/java/auth_core/src/test/resources/ec_private_param_secp384r1.key new file mode 100644 index 00000000000..4cbb7eb1303 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/ec_private_param_secp384r1.key @@ -0,0 +1,9 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDDfyvxnpfL5PQBl1eUlQ09dkIiRkoVyY6YYJI7DiwQi3epamZye2Kt1 +FJoRbp3UrN6gBwYFK4EEACKhZANiAATVBbfnk91BwVCBykodWJQcP1wrR2h/0Jgj +Phx3TCDZB8SARxu6xwD0Z5DZQ4QsMLAKRm2TvUjULhMjU3Wc/b2xX6j5TrhdisU5 +ekZmMQCIaXYm4gzbgDXY/HaHPUaCn9I= +-----END EC PRIVATE KEY----- diff --git a/libs/java/auth_core/src/test/resources/ec_private_params.key b/libs/java/auth_core/src/test/resources/ec_private_params.key new file mode 100644 index 00000000000..fded1e9e310 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/ec_private_params.key @@ -0,0 +1,13 @@ +-----BEGIN EC PARAMETERS----- +MIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP////////// +/////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY12Ko6 +k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3gZ9+ +kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO5+tK +fA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racXnoTz +ucrC/GMlUQIBAQ== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIOVUPQXm03iv8HSPrhhTvo3VDbcsow8k6t/SAz0U3xkAoAoGCCqGSM49 +AwEHoUQDQgAE30K6pyN44ELKYoSFPlxeWMBpoqcZ092OKhHBXOOUBo9uSmMwHq6Z +wXkHZJclrPCbK3Y4Qxf/l/YW+v0T56A8vQ== +-----END EC PRIVATE KEY----- diff --git a/libs/java/auth_core/src/test/resources/ec_public.key b/libs/java/auth_core/src/test/resources/ec_public.key new file mode 100644 index 00000000000..e3cf117cf63 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/ec_public.key @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjTHrARSTlPSGyVpPzcM1XLmv3xec +bscCNDKeMKtx0J4BN1XZ5ul5+oGWL9JdnC8vf7s6IPcxOvIZtH7NFIVn+w== +-----END PUBLIC KEY----- diff --git a/libs/java/auth_core/src/test/resources/ec_public_invalid.key b/libs/java/auth_core/src/test/resources/ec_public_invalid.key new file mode 100644 index 00000000000..5aca0dfd8ac --- /dev/null +++ b/libs/java/auth_core/src/test/resources/ec_public_invalid.key @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQMDMgAE+Y+qPqI3geo2hQH8eK7Rn+YWG09T +ejZ5QFoj9fmxFrUyYhFap6XmTdJtEi8myBmW +-----END PUBLIC KEY----- diff --git a/libs/java/auth_core/src/test/resources/ec_public_param_prime256v1.key b/libs/java/auth_core/src/test/resources/ec_public_param_prime256v1.key new file mode 100644 index 00000000000..e36acd1cc2d --- /dev/null +++ b/libs/java/auth_core/src/test/resources/ec_public_param_prime256v1.key @@ -0,0 +1,7 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE30K6pyN44ELKYoSFPlxeWMBpoqcZ +092OKhHBXOOUBo9uSmMwHq6ZwXkHZJclrPCbK3Y4Qxf/l/YW+v0T56A8vQ== +-----END PUBLIC KEY----- diff --git a/libs/java/auth_core/src/test/resources/ec_public_param_secp384r1.key b/libs/java/auth_core/src/test/resources/ec_public_param_secp384r1.key new file mode 100644 index 00000000000..08404424b00 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/ec_public_param_secp384r1.key @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIg== +-----END EC PARAMETERS----- +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1QW355PdQcFQgcpKHViUHD9cK0dof9CY +Iz4cd0wg2QfEgEcbuscA9GeQ2UOELDCwCkZtk71I1C4TI1N1nP29sV+o+U64XYrF +OXpGZjEAiGl2JuIM24A12Px2hz1Ggp/S +-----END PUBLIC KEY----- diff --git a/libs/java/auth_core/src/test/resources/ec_public_params.key b/libs/java/auth_core/src/test/resources/ec_public_params.key new file mode 100644 index 00000000000..8ec3b5d68ff --- /dev/null +++ b/libs/java/auth_core/src/test/resources/ec_public_params.key @@ -0,0 +1,12 @@ +-----BEGIN EC PARAMETERS----- +MIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP////////// +/////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY12Ko6 +k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3gZ9+ +kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO5+tK +fA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racXnoTz +ucrC/GMlUQIBAQ== +-----END EC PARAMETERS----- +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE30K6pyN44ELKYoSFPlxeWMBpoqcZ +092OKhHBXOOUBo9uSmMwHq6ZwXkHZJclrPCbK3Y4Qxf/l/YW+v0T56A8vQ== +-----END PUBLIC KEY----- diff --git a/libs/java/auth_core/src/test/resources/ec_public_x509.cert b/libs/java/auth_core/src/test/resources/ec_public_x509.cert new file mode 100644 index 00000000000..c412de8a3ca --- /dev/null +++ b/libs/java/auth_core/src/test/resources/ec_public_x509.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICejCCAiKgAwIBAgIJAKxhW5mQ2i8xMAkGByqGSM49BAEwYDELMAkGA1UEBhMC +VVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxGDAWBgNVBAoTD015 +IFRlc3QgQ29tcGFueTEWMBQGA1UEAxMNYXRoZW56LnN5bmNlcjAeFw0xNjEyMDky +MjA0NTdaFw0xNzEyMDkyMjA0NTdaMGAxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD +QTESMBAGA1UEBxMJU3Vubnl2YWxlMRgwFgYDVQQKEw9NeSBUZXN0IENvbXBhbnkx +FjAUBgNVBAMTDWF0aGVuei5zeW5jZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AASNMesBFJOU9IbJWk/NwzVcua/fF5xuxwI0Mp4wq3HQngE3Vdnm6Xn6gZYv0l2c +Ly9/uzog9zE68hm0fs0UhWf7o4HFMIHCMB0GA1UdDgQWBBRzUG6PZe6W5zH0hYKu +OAFCNbpU6TCBkgYDVR0jBIGKMIGHgBRzUG6PZe6W5zH0hYKuOAFCNbpU6aFkpGIw +YDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUx +GDAWBgNVBAoTD015IFRlc3QgQ29tcGFueTEWMBQGA1UEAxMNYXRoZW56LnN5bmNl +coIJAKxhW5mQ2i8xMAwGA1UdEwQFMAMBAf8wCQYHKoZIzj0EAQNHADBEAiBY+KCi +NMjkPod8Cx9Iufy9sfPjohEsWtjhAhLpDDgxxgIgaaKNCn7SIWYyelqyn41VMazv +4oAZqrR8bLL/qllF0bg= +-----END CERTIFICATE----- diff --git a/libs/java/auth_core/src/test/resources/example.keytab b/libs/java/auth_core/src/test/resources/example.keytab new file mode 100644 index 0000000000000000000000000000000000000000..9a51564110634f3b7ac9974fe5f9056957468866 GIT binary patch literal 118 zcmZQ&Vqjn}V_;<9c8zfK4e)W*bN2UT;K;2kPAw`+Edok1hBqInU}O+y5D1Pf`{eZL q=O@R7(|+a5Ja3IdXL3$vYF-J7PVHwmb+?zUd6{@LN7hI3V-o;p=_Ew} literal 0 HcmV?d00001 diff --git a/libs/java/auth_core/src/test/resources/fantasy_private_k0.key b/libs/java/auth_core/src/test/resources/fantasy_private_k0.key new file mode 100644 index 00000000000..cc7d40de644 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/fantasy_private_k0.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCu0nOEra8WmmU91u2KrDdcKRDcZn3oSwsZD/55d0bkMwEiMzfQ ++xHVRFI1PPGjhG167oRhTRKE3a3uakMGmMDM5WWcDLbLo+PHZGqUyhJrvq5BF4VW +rUWpY+rppaklBTUPY0asmlObVpFBVoujkSyxMIXmOi9qK/O+Bs0BI4jo6QIDAQAB +AoGAS0MCxF3ZgMubRlIfFZIqixyKy7e8AKM99d4y2awF8vwaQtT19JwCA2RUV+MS +zCiY0VGZ4CHEFTsyQ++vR0m/xj88HtKPu7lRh+ITZ/FrF+7YAXCo/eQoLJaqbKFa +sU33eMj1QgMETvsfs1xWmXi5reuOtsRn8nIfbFK4mrGCyFkCQQDl3vqlDdWv2cXa +er+wPmApeAkxT0AAkIUyhgYCkZHIigBFY2Y6Qq1mcFHpWIIdGbQ1HnJ53clDpI6a +KB2GPWzjAkEAwrGaI9JHZ9wxKQB9wHaFehnHhuGOBGS0/6AzNrlnfybejSo4kBkv +tTxCoVrInJqq6PYDHuUHb/7qvJLD+66owwJAHGLdttYvhiixWxp/Y2dAfr54/CLa +N3ehSyzrvxN02jvYbrkonZcwTI8gPl2Uq71J2Klq34u41+aRSY4cn0AkmwJAJS6N +5XFvOaKPsOjrGyqHaz4pINVKrgQdnQQXQ8g8v8fIkOUTUFG/Drmnb/FKhr3zDfKN +vgS3WugwJDtTOmmsyQJBAN3gJq/JMvTeg3Oiecqd/hMgKxb82JmKpzaWXPh2+lN+ +oON8z+dgMRU266fTzAflANdDx9q5tlWkrySQJ9SK0Sg= +-----END RSA PRIVATE KEY----- diff --git a/libs/java/auth_core/src/test/resources/fantasy_private_k1.key b/libs/java/auth_core/src/test/resources/fantasy_private_k1.key new file mode 100644 index 00000000000..289cecb9f24 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/fantasy_private_k1.key @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAJx5ylKcnuRVECR2qLEbEvfoUZ787Mu9qoKn3p8azR8iZ7jG09w/ +MDryxpdUeEUjK8pBU1v+c7MKwmJPLoA4rasCAwEAAQJABt592ESR4Rou3DrtSuES +Yxmqw5z81DDeVGAEMN3fVVd+vVy16m1VLJLWpYwsmRoFlLauz0W7oRGFwoxuzSpP +oQIhAMo3hlrumTIjOrmGHdmQJGUbPWc35m3j+iMdNAlfeR47AiEAxhfbiuPtZCmx +LguI03LTpfZEHt1OIdsQXtdJHguPh1ECIH0Q7ErPKPambUqTFWEa0jeqKkbtftnW +Vz1icW2em9VDAiEAsfZrAMsl0Q93otMv+C63n+ivaD8PW3EPsvScNobSShECIQCG +GNn+Twi8sQe22pyk65yyZ0wJNm+ioTpRgg6NHJJx0w== +-----END RSA PRIVATE KEY----- diff --git a/libs/java/auth_core/src/test/resources/fantasy_public_k0.key b/libs/java/auth_core/src/test/resources/fantasy_public_k0.key new file mode 100644 index 00000000000..0a6117fb736 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/fantasy_public_k0.key @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCu0nOEra8WmmU91u2KrDdcKRDc +Zn3oSwsZD/55d0bkMwEiMzfQ+xHVRFI1PPGjhG167oRhTRKE3a3uakMGmMDM5WWc +DLbLo+PHZGqUyhJrvq5BF4VWrUWpY+rppaklBTUPY0asmlObVpFBVoujkSyxMIXm +Oi9qK/O+Bs0BI4jo6QIDAQAB +-----END PUBLIC KEY----- diff --git a/libs/java/auth_core/src/test/resources/fantasy_public_k1.key b/libs/java/auth_core/src/test/resources/fantasy_public_k1.key new file mode 100644 index 00000000000..3ffe0c4a55a --- /dev/null +++ b/libs/java/auth_core/src/test/resources/fantasy_public_k1.key @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJx5ylKcnuRVECR2qLEbEvfoUZ787Mu9 +qoKn3p8azR8iZ7jG09w/MDryxpdUeEUjK8pBU1v+c7MKwmJPLoA4rasCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/libs/java/auth_core/src/test/resources/host_private.key b/libs/java/auth_core/src/test/resources/host_private.key new file mode 100644 index 00000000000..5d5faa50a71 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/host_private.key @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAPZrZC1zPa5pPfZ1iqmr7gYoXhuHliRPUnyKzYbYhQezTJReH0lu +hoUWPu6qydGJnxEU2NWMCXY/8n/UFIFojF0CAwEAAQJAALfZZ+SBFoQKATDggZQV +soDlnVDs2Bg/FkvVQ4JYCOi0ZqOGjsqplvw7vfML14zPbT7g7gnFCT5TXYzO8qYB +xQIhAPzUNTbMbiQCf5leOn5tLj5imgDtFfy185wbjIt1Sx/DAiEA+YKakkhvHqBP ++cJQaBRneLt5RJXDsEB7xc3uaER4AV8CIHYApYWaDJ4J/Hwcmrh/ROIhKzfbcDOu +yLDHuuUsLY/5AiEAt+LVYHIZ0wx7ZKsc71f6WjRwz2dA7ajYj5OR3S548ykCIQCC +W9QxTqfLoDGTj3Oe5rSyDquXyOz+45hfsZCuLniQkA== +-----END RSA PRIVATE KEY----- diff --git a/libs/java/auth_core/src/test/resources/host_public.key b/libs/java/auth_core/src/test/resources/host_public.key new file mode 100644 index 00000000000..3c715a4b69d --- /dev/null +++ b/libs/java/auth_core/src/test/resources/host_public.key @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPZrZC1zPa5pPfZ1iqmr7gYoXhuHliRP +UnyKzYbYhQezTJReH0luhoUWPu6qydGJnxEU2NWMCXY/8n/UFIFojF0CAwEAAQ== +-----END PUBLIC KEY----- diff --git a/libs/java/auth_core/src/test/resources/invalid.csr b/libs/java/auth_core/src/test/resources/invalid.csr new file mode 100644 index 00000000000..1c91ec5d216 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/invalid.csr @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UE +BwwLUGxheWEgVmlzdGExDjAMBgNVBAoMBVlhaG9vMQ8wDQYDVQQLDAZBdGhlbnMx +GzAZBgNVBAMMEmlhYXMuYXRoZW5zLnN5bmNlcjEgMB4GCSqGSIb3DQEJARYRaGdh +V2sByaU70NtHiwaj2GT/AgMBAAGgADANBgkqhkiG9w0BAQUFAAOCAQEAnrauei1l +8RjvayD7bWzHRAWQJNV6LViMDSxZ1SG/6T0+U29g8aX1cFyV6BUnkRiqpYYjFSRG +vbHddRkbqRSMQTt5ceRgiPkdFILl99xHQk4XFOi8b2I4Jdo8pKh31+4p2b/l7BaK +4Dgum7RQ8y2l53oEm+u4Kc9jA0Pu8KtA7Mvr5bBzQnHk5MIKTkDx+/HYMcaX9atD +7GSpdKm/1/NwejCq21uhBiJ6/MXjtbNp4gYqNDz9VF/QxiXHaEBh+uE91Jpw9Z2b +1yc9CSuZK/MNxnu9LtHkVjoHXMFGk+0KAbBfLFABavxmdUQPpUVbi/KuwsaWjv5k +SZZG9SYopxApdQ== +-----END CERTIFICATE REQUEST----- diff --git a/libs/java/auth_core/src/test/resources/invalid_x509.cert b/libs/java/auth_core/src/test/resources/invalid_x509.cert new file mode 100644 index 00000000000..45a6c860720 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/invalid_x509.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIDpDCCAowCCQCSBmRz8z0ZPjANBgkqhkiG9w0BAQsFADCBljELMAkGA1UEBhMC +VVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQHDAtQbGF5YSBWaXN0YTEOMAwGA1UECgwF +WWFob28xDzANBgNVBAsMBkF0aGVuczEhMB8GA1UEAwwYZGV2Lnp0cy5hdGhlbnMu +eWFob28uY29tMSAwHgYJKoZIhvcNAQkBFhFoZ2FAeWFob28taW5jLmNvbTAeFw0x +NjA1MDMyMjMwNTBaFw0xNzA5MTUyMjMwNTBaMIGQMQswCQYDVQQGEwJVUzELMAkG +A1UECAwCQdExFDASBgNVBAcMC1BsYXlhIFZpc3RhMQ4wDAYDVQQKDAVZYWhvbzEP +MA0GA1UECwwGQXRoZW5zMRswGQYDVQQDDBJpYWFzLmF0aGVucy5zeW5jZXIxIDAe +G2s0Stq/0pPa5smOkKOHuVdrAcmlO9DbR4sGo9hk/wIDAQABMA0GCSqGSIb3DQEB +CwUAA4IBAQA0wzJiYmesbpyhsDYajZpNi8+9cVj8KRApPDbL8FKjiWCV6uw6dHck +cnzt+GtBnxmMKsb9peLX0PhXdrVJ45mHOQZBx6OlOuJUa0lf7JBx1090s6l29h26 +xnruQK4r4KUgC5vwRCMF4jTnkgPmg29ZLgv/lOS0oG5jdXVQ4jia5A8Kq5NeF9e2 +xImHe841c+ovp5iLZJiX/BY26tnNHB1U7t52cqaB+7Oykvhf5yztFle0f6twUihD +0ZH9MU3dKfVvydmNL1d+xGqWjqewS2UZx0byiqFxU94HsIrSKUdzYUt6Qz0Rh+Ym +yzjOd9NVpJl62Rekkhhg6YCLiaHNII3H +-----END CERTIFICATE----- diff --git a/libs/java/auth_core/src/test/resources/jaas.conf b/libs/java/auth_core/src/test/resources/jaas.conf new file mode 100644 index 00000000000..15d8f4842ce --- /dev/null +++ b/libs/java/auth_core/src/test/resources/jaas.conf @@ -0,0 +1,14 @@ + +Client { +com.sun.security.auth.module.Krb5LoginModule required +useTicketCache=false; +}; + +Server { +com.sun.security.auth.module.Krb5LoginModule required +useKeyTab=false +storeKey=true +useTicketCache=true +principal="myserver@LOCALHOST"; +}; + diff --git a/libs/java/auth_core/src/test/resources/logback.xml b/libs/java/auth_core/src/test/resources/logback.xml new file mode 100644 index 00000000000..c4a0357b8d5 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + System.out + + %-4relative [%thread] %-5level %class - %msg%n + + + + + + + + + + diff --git a/libs/java/auth_core/src/test/resources/private_encrypted.key b/libs/java/auth_core/src/test/resources/private_encrypted.key new file mode 100644 index 00000000000..dbefd0227b0 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/private_encrypted.key @@ -0,0 +1,10 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIBeTAbBgkqhkiG9w0BBQMwDgQITzfDOS9/ungCAggABIIBWEc5vdvKj55j5NRs +fFmvBeF/OoTJ2/M1yClq4D3RILOzwb8HQ2EN6YtlOxzQK9r/5FXUi9sgYPi9nrHG +4pQFFpQapZ7TbIYZ1As6WXHDaju1Dt+mO5See11TV8VW6SvAk1V8mWcGnwpaqH2g +ZdGb1tmOxky0kCr31rb0mpWp6kFccvkjX+dve2K4AWS59SeK5MVdjiBFRyQ2+qga +rFp4kCn15orSc2xrkyiAXiacUvs1ixp7poJCv2y7oAcItEJ1pAjV8UHEzau0rgHq +JJhJGD6d5o1Pt4dMtQhdxOvjpPfz6d3qFHIoYNwYAtkVxHowQ8Uv0I3sXjKFwMqU +zqztXXYH23BOq+OrxDNIiqnNtbOOhrv2SE2Tn342OGd001JfgXhyJSPugztN5NvB +L5o4dPYUp1wbBBoG52nzqd44LDy1v5tyzijhlyoo+YzVwfiMHK1rb8sEwO4U +-----END ENCRYPTED PRIVATE KEY----- diff --git a/libs/java/auth_core/src/test/resources/rsa_private.key b/libs/java/auth_core/src/test/resources/rsa_private.key new file mode 100644 index 00000000000..81f117ed93b --- /dev/null +++ b/libs/java/auth_core/src/test/resources/rsa_private.key @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBANImoEeD7LU+x6x5dAnNRFtrbab2yxrDXFXdwKQxlIs2PWfLeo3X +q0e2bOU6kQDgFz/IkMN3tEyDgQ/Nv0eAbckCAwEAAQJBAJnp3lPihHhOTbG9lkYj +h6ApMzso36JvWO4upovbID9AGyzOPstX9VsAFosgL0pTAJkqDKO2lawpkBclCtkS +xj0CIQD16P1g1oIlTVk9dCmyn94ZgXpHD6RMhfhOef8xYGVaSwIhANrGBpFY9cjH +k5rZy3vRyuWpeEzs1BYD5KH94grx2su7AiEA8QRnescjjb3uzk2RJNKNk4AUwZsy +FBJmWTw3A0UQcVsCIQDZuNX0dLo2lRg6taGimkj4gs3skI0JU9qHtKjvB38d3QIf +TVbliFn82h1AtxxlWrb5yWF4PtBtFgSz2D/aHVBkVQ== +-----END RSA PRIVATE KEY----- diff --git a/libs/java/auth_core/src/test/resources/rsa_public.key b/libs/java/auth_core/src/test/resources/rsa_public.key new file mode 100644 index 00000000000..ed64f02f408 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/rsa_public.key @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANImoEeD7LU+x6x5dAnNRFtrbab2yxrD +XFXdwKQxlIs2PWfLeo3Xq0e2bOU6kQDgFz/IkMN3tEyDgQ/Nv0eAbckCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/libs/java/auth_core/src/test/resources/rsa_public_invalid.key b/libs/java/auth_core/src/test/resources/rsa_public_invalid.key new file mode 100644 index 00000000000..7b8773224ac --- /dev/null +++ b/libs/java/auth_core/src/test/resources/rsa_public_invalid.key @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKS0oEvv+u9VDzjTOwdjaUJoBVm9ess0 +GYl/xA0nhBgz/VJSIs4IcV++5UaW+rSDTn/8k+wy0sIJiVLsBIOMFGcCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/libs/java/auth_core/src/test/resources/rsa_public_x509.cert b/libs/java/auth_core/src/test/resources/rsa_public_x509.cert new file mode 100644 index 00000000000..805615e10e5 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/rsa_public_x509.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICfzCCAimgAwIBAgIJAMIRugkwe7mwMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRgwFgYDVQQK +Ew9NeSBUZXN0IENvbXBhbnkxFjAUBgNVBAMTDWF0aGVuei5zeW5jZXIwHhcNMTYx +MjA5MjE1NDQ2WhcNMTcxMjA5MjE1NDQ2WjBgMQswCQYDVQQGEwJVUzELMAkGA1UE +CBMCQ0ExEjAQBgNVBAcTCVN1bm55dmFsZTEYMBYGA1UEChMPTXkgVGVzdCBDb21w +YW55MRYwFAYDVQQDEw1hdGhlbnouc3luY2VyMFwwDQYJKoZIhvcNAQEBBQADSwAw +SAJBANImoEeD7LU+x6x5dAnNRFtrbab2yxrDXFXdwKQxlIs2PWfLeo3Xq0e2bOU6 +kQDgFz/IkMN3tEyDgQ/Nv0eAbckCAwEAAaOBxTCBwjAdBgNVHQ4EFgQUBi0msMNX +iC2XLpKjXPz1DMf9PoEwgZIGA1UdIwSBijCBh4AUBi0msMNXiC2XLpKjXPz1DMf9 +PoGhZKRiMGAxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vu +bnl2YWxlMRgwFgYDVQQKEw9NeSBUZXN0IENvbXBhbnkxFjAUBgNVBAMTDWF0aGVu +ei5zeW5jZXKCCQDCEboJMHu5sDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A0EAPnVkUdicQzk1809lHNyhSqb6p+kxjC52RfI8bZ+kACOppLLCpNu3nuuh+9yT +J1U2YLjE774n48u74k7EEmvJ3A== +-----END CERTIFICATE----- diff --git a/libs/java/auth_core/src/test/resources/valid.csr b/libs/java/auth_core/src/test/resources/valid.csr new file mode 100644 index 00000000000..6c6bbd517be --- /dev/null +++ b/libs/java/auth_core/src/test/resources/valid.csr @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBGjCBxQIBADBgMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcT +CVN1bm55dmFsZTEYMBYGA1UEChMPTXkgVGVzdCBDb21wYW55MRYwFAYDVQQDEw1h +dGhlbnouc3luY2VyMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKrvfvBgXWqWAorw +5hYJu3dpOJe0gp3nTgiiPGT7+jzm6BRcssOBTPFIMkePT2a8Tq+FYSmFnHfbQjwm +Yw2uMK8CAwEAAaAAMA0GCSqGSIb3DQEBBQUAA0EANa7mSdMKoQc1OZPM6qhpp2Vi +49bRxbXLvRLE9f6rbIDkvjgPenpLw2xWgOdtwYcTy9TK0PCWB6dOSk4fkN+bBQ== +-----END CERTIFICATE REQUEST----- diff --git a/libs/java/auth_core/src/test/resources/valid_cn_x509.cert b/libs/java/auth_core/src/test/resources/valid_cn_x509.cert new file mode 100644 index 00000000000..805615e10e5 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/valid_cn_x509.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICfzCCAimgAwIBAgIJAMIRugkwe7mwMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRgwFgYDVQQK +Ew9NeSBUZXN0IENvbXBhbnkxFjAUBgNVBAMTDWF0aGVuei5zeW5jZXIwHhcNMTYx +MjA5MjE1NDQ2WhcNMTcxMjA5MjE1NDQ2WjBgMQswCQYDVQQGEwJVUzELMAkGA1UE +CBMCQ0ExEjAQBgNVBAcTCVN1bm55dmFsZTEYMBYGA1UEChMPTXkgVGVzdCBDb21w +YW55MRYwFAYDVQQDEw1hdGhlbnouc3luY2VyMFwwDQYJKoZIhvcNAQEBBQADSwAw +SAJBANImoEeD7LU+x6x5dAnNRFtrbab2yxrDXFXdwKQxlIs2PWfLeo3Xq0e2bOU6 +kQDgFz/IkMN3tEyDgQ/Nv0eAbckCAwEAAaOBxTCBwjAdBgNVHQ4EFgQUBi0msMNX +iC2XLpKjXPz1DMf9PoEwgZIGA1UdIwSBijCBh4AUBi0msMNXiC2XLpKjXPz1DMf9 +PoGhZKRiMGAxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vu +bnl2YWxlMRgwFgYDVQQKEw9NeSBUZXN0IENvbXBhbnkxFjAUBgNVBAMTDWF0aGVu +ei5zeW5jZXKCCQDCEboJMHu5sDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A0EAPnVkUdicQzk1809lHNyhSqb6p+kxjC52RfI8bZ+kACOppLLCpNu3nuuh+9yT +J1U2YLjE774n48u74k7EEmvJ3A== +-----END CERTIFICATE----- diff --git a/libs/java/auth_core/src/test/resources/zts_private_k0.key b/libs/java/auth_core/src/test/resources/zts_private_k0.key new file mode 100644 index 00000000000..cc7d40de644 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/zts_private_k0.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCu0nOEra8WmmU91u2KrDdcKRDcZn3oSwsZD/55d0bkMwEiMzfQ ++xHVRFI1PPGjhG167oRhTRKE3a3uakMGmMDM5WWcDLbLo+PHZGqUyhJrvq5BF4VW +rUWpY+rppaklBTUPY0asmlObVpFBVoujkSyxMIXmOi9qK/O+Bs0BI4jo6QIDAQAB +AoGAS0MCxF3ZgMubRlIfFZIqixyKy7e8AKM99d4y2awF8vwaQtT19JwCA2RUV+MS +zCiY0VGZ4CHEFTsyQ++vR0m/xj88HtKPu7lRh+ITZ/FrF+7YAXCo/eQoLJaqbKFa +sU33eMj1QgMETvsfs1xWmXi5reuOtsRn8nIfbFK4mrGCyFkCQQDl3vqlDdWv2cXa +er+wPmApeAkxT0AAkIUyhgYCkZHIigBFY2Y6Qq1mcFHpWIIdGbQ1HnJ53clDpI6a +KB2GPWzjAkEAwrGaI9JHZ9wxKQB9wHaFehnHhuGOBGS0/6AzNrlnfybejSo4kBkv +tTxCoVrInJqq6PYDHuUHb/7qvJLD+66owwJAHGLdttYvhiixWxp/Y2dAfr54/CLa +N3ehSyzrvxN02jvYbrkonZcwTI8gPl2Uq71J2Klq34u41+aRSY4cn0AkmwJAJS6N +5XFvOaKPsOjrGyqHaz4pINVKrgQdnQQXQ8g8v8fIkOUTUFG/Drmnb/FKhr3zDfKN +vgS3WugwJDtTOmmsyQJBAN3gJq/JMvTeg3Oiecqd/hMgKxb82JmKpzaWXPh2+lN+ +oON8z+dgMRU266fTzAflANdDx9q5tlWkrySQJ9SK0Sg= +-----END RSA PRIVATE KEY----- diff --git a/libs/java/auth_core/src/test/resources/zts_private_k1.key b/libs/java/auth_core/src/test/resources/zts_private_k1.key new file mode 100644 index 00000000000..5d5faa50a71 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/zts_private_k1.key @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAPZrZC1zPa5pPfZ1iqmr7gYoXhuHliRPUnyKzYbYhQezTJReH0lu +hoUWPu6qydGJnxEU2NWMCXY/8n/UFIFojF0CAwEAAQJAALfZZ+SBFoQKATDggZQV +soDlnVDs2Bg/FkvVQ4JYCOi0ZqOGjsqplvw7vfML14zPbT7g7gnFCT5TXYzO8qYB +xQIhAPzUNTbMbiQCf5leOn5tLj5imgDtFfy185wbjIt1Sx/DAiEA+YKakkhvHqBP ++cJQaBRneLt5RJXDsEB7xc3uaER4AV8CIHYApYWaDJ4J/Hwcmrh/ROIhKzfbcDOu +yLDHuuUsLY/5AiEAt+LVYHIZ0wx7ZKsc71f6WjRwz2dA7ajYj5OR3S548ykCIQCC +W9QxTqfLoDGTj3Oe5rSyDquXyOz+45hfsZCuLniQkA== +-----END RSA PRIVATE KEY----- diff --git a/libs/java/auth_core/src/test/resources/zts_public_k0.key b/libs/java/auth_core/src/test/resources/zts_public_k0.key new file mode 100644 index 00000000000..0a6117fb736 --- /dev/null +++ b/libs/java/auth_core/src/test/resources/zts_public_k0.key @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCu0nOEra8WmmU91u2KrDdcKRDc +Zn3oSwsZD/55d0bkMwEiMzfQ+xHVRFI1PPGjhG167oRhTRKE3a3uakMGmMDM5WWc +DLbLo+PHZGqUyhJrvq5BF4VWrUWpY+rppaklBTUPY0asmlObVpFBVoujkSyxMIXm +Oi9qK/O+Bs0BI4jo6QIDAQAB +-----END PUBLIC KEY----- diff --git a/libs/java/auth_core/src/test/resources/zts_public_k1.key b/libs/java/auth_core/src/test/resources/zts_public_k1.key new file mode 100644 index 00000000000..3c715a4b69d --- /dev/null +++ b/libs/java/auth_core/src/test/resources/zts_public_k1.key @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPZrZC1zPa5pPfZ1iqmr7gYoXhuHliRP +UnyKzYbYhQezTJReH0luhoUWPu6qydGJnxEU2NWMCXY/8n/UFIFojF0CAwEAAQ== +-----END PUBLIC KEY----- diff --git a/libs/java/client_common/README.md b/libs/java/client_common/README.md new file mode 100644 index 00000000000..39636ac199b --- /dev/null +++ b/libs/java/client_common/README.md @@ -0,0 +1,12 @@ +# Athenz Common Classes + +Common libs for clients and servers + +- monitoring metrics: Metric and MetricFactory interfaces with no-op implementation +- sign utilities: Provide canonical json representation of an object being signed + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/libs/java/client_common/pom.xml b/libs/java/client_common/pom.xml new file mode 100644 index 00000000000..9eb5b648356 --- /dev/null +++ b/libs/java/client_common/pom.xml @@ -0,0 +1,43 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../../pom.xml + + + client_common + client_common + Athenz Client Common Package + jar + + + + ${project.groupId} + zms_core + ${project.parent.version} + + + ${project.groupId} + zts_core + ${project.parent.version} + + + + diff --git a/libs/java/client_common/src/main/java/com/yahoo/athenz/common/config/AthenzConfig.java b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/config/AthenzConfig.java new file mode 100644 index 00000000000..0c2b0f70551 --- /dev/null +++ b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/config/AthenzConfig.java @@ -0,0 +1,54 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.config; + +import java.util.ArrayList; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.yahoo.athenz.zms.PublicKeyEntry; + +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class AthenzConfig { + private String zmsUrl; + private String ztsUrl; + private ArrayList zmsPublicKeys; + private ArrayList ztsPublicKeys; + + public String getZmsUrl() { + return zmsUrl; + } + public void setZmsUrl(String zmsUrl) { + this.zmsUrl = zmsUrl; + } + public String getZtsUrl() { + return ztsUrl; + } + public void setZtsUrl(String ztsUrl) { + this.ztsUrl = ztsUrl; + } + public ArrayList getZmsPublicKeys() { + return zmsPublicKeys; + } + public void setZmsPublicKeys(ArrayList zmsPublicKeys) { + this.zmsPublicKeys = zmsPublicKeys; + } + public ArrayList getZtsPublicKeys() { + return ztsPublicKeys; + } + public void setZtsPublicKeys(ArrayList ztsPublicKeys) { + this.ztsPublicKeys = ztsPublicKeys; + } +} diff --git a/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/Metric.java b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/Metric.java new file mode 100644 index 00000000000..7ddeb35cceb --- /dev/null +++ b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/Metric.java @@ -0,0 +1,77 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.metrics; + +public interface Metric { + + /** + * Increment the counter for the specified metric + * @param metric Name of the counter + */ + public void increment(String metric); + + /** + * Increment the counter for the specified metric for the given domainName + * @param metric Name of the counter + * @param domainName Name of the domain. domainName is optional can be + * passed as null to indicate that the counter is global and not per-domain + */ + public void increment(String metric, String domainName); + + /** + * Increment the sum by the specified count for the given metric against the domainName + * @param metric Name of the counter + * @param domainName Name of the domain. domainName is optional can be + * passed as null to indicate that the counter is global and not per-domain + * @param count amount inwhich to increment the metric sum + */ + public void increment(String metric, String domainName, int count); + + /** + * Start the latency timer for the specified metric for the given domainName. + * The implementation must be able to support simultaneous handling of + * multiple timer counters (but not the same metric). It's possible that + * the application/lib started a latency timer for a metric but will not call + * the stopTiming method of the request didn't complete successfully since + * we only want to keep track of average latency time for successfully + * completed requests. + * @param metric Name of the counter + * @param domainName Name of the domain. domainName is optional can be + * passed as null to indicate that the counter is global and not per-domain + * @return timer object. The server will use this as the argument to + * the stopTiming method to indicate that the operation has completed + * and the time must be recorded for the metric. + */ + public Object startTiming(String metric, String domainName); + + /** + * Stop the latency timer for the specified metric. + * @param timerMetric timer object that was returned by the startTiming + * method call. + */ + public void stopTiming(Object timerMetric); + + /** + * Flush any buffered metrics to destination. + */ + public void flush(); + + /** + * Flush buffers and shutdown any tasks. + */ + public void quit(); +} + diff --git a/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/MetricFactory.java b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/MetricFactory.java new file mode 100644 index 00000000000..01668efba22 --- /dev/null +++ b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/MetricFactory.java @@ -0,0 +1,26 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.metrics; + +public interface MetricFactory { + + /** + * Create and return a new Metric instance + * @return Metric instance + */ + public Metric create(); +} + diff --git a/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/impl/NoOpMetric.java b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/impl/NoOpMetric.java new file mode 100644 index 00000000000..72ff5cd08e7 --- /dev/null +++ b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/impl/NoOpMetric.java @@ -0,0 +1,57 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.metrics.impl; + +import com.yahoo.athenz.common.metrics.Metric; + +public class NoOpMetric implements Metric { + + /** + * Constructs a new NoOpMetric object in which all methods are stubs. + * No metrics are recorded with this implementation. + */ + public NoOpMetric() { + } + + @Override + public void increment(String metric) { + } + + @Override + public void increment(String metric, String domainName) { + } + + @Override + public void increment(String metric, String domainName, int count) { + } + + @Override + public Object startTiming(String metric, String domainName) { + return null; + } + + @Override + public void stopTiming(Object timerMetric) { + } + + @Override + public void flush() { + } + + @Override + public void quit() { + } +} diff --git a/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/impl/NoOpMetricFactory.java b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/impl/NoOpMetricFactory.java new file mode 100644 index 00000000000..33c9052f0a3 --- /dev/null +++ b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/metrics/impl/NoOpMetricFactory.java @@ -0,0 +1,27 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.metrics.impl; + +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.metrics.MetricFactory; + +public class NoOpMetricFactory implements MetricFactory { + + @Override + public Metric create() { + return new NoOpMetric(); + } +} diff --git a/libs/java/client_common/src/main/java/com/yahoo/athenz/common/utils/SignUtils.java b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/utils/SignUtils.java new file mode 100644 index 00000000000..181150b32e6 --- /dev/null +++ b/libs/java/client_common/src/main/java/com/yahoo/athenz/common/utils/SignUtils.java @@ -0,0 +1,286 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.utils; + +import java.util.List; + +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.DomainData; +import com.yahoo.athenz.zms.DomainPolicies; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.PublicKeyEntry; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.ServiceIdentity; +import com.yahoo.athenz.zms.SignedPolicies; +import com.yahoo.athenz.zts.PolicyData; +import com.yahoo.athenz.zts.SignedPolicyData; +import com.yahoo.rdl.Array; +import com.yahoo.rdl.Struct; + +public class SignUtils { + + private static final String ATTR_MODIFIED = "modified"; + private static final String ATTR_POLICIES = "policies"; + private static final String ATTR_DOMAIN = "domain"; + private static final String ATTR_EXPIRES = "expires"; + private static final String ATTR_POLICY_DATA = "policyData"; + private static final String ATTR_ZMS_SIGNATURE = "zmsSignature"; + private static final String ATTR_ZMS_KEY_ID = "zmsKeyId"; + private static final String ATTR_MEMBERS = "members"; + private static final String ATTR_NAME = "name"; + private static final String ATTR_ROLE = "role"; + private static final String ATTR_SERVICES = "services"; + private static final String ATTR_ID = "id"; + private static final String ATTR_PUBLIC_KEYS = "publicKeys"; + private static final String ATTR_ACCOUNT = "account"; + private static final String ATTR_PRODUCT_ID = "ypmId"; + private static final String ATTR_EFFECT = "effect"; + private static final String ATTR_ACTION = "action"; + private static final String ATTR_RESOURCE = "resource"; + private static final String ATTR_ASSERTIONS = "assertions"; + private static final String ATTR_EXECUTABLE = "executable"; + private static final String ATTR_TRUST = "trust"; + private static final String ATTR_GROUP = "group"; + private static final String ATTR_PROVIDER_ENDPOINT = "providerEndpoint"; + private static final String ATTR_USER = "user"; + private static final String ATTR_HOSTS = "hosts"; + private static final String ATTR_KEY = "key"; + private static final String ATTR_ROLES = "roles"; + private static final String ATTR_SIGNATURE = "signature"; + private static final String ATTR_KEYID = "keyId"; + private static final String ATTR_CONTENTS = "contents"; + + private static Struct asStruct(DomainPolicies domainPolicies) { + Struct struct = new Struct(); + struct.append(ATTR_DOMAIN, domainPolicies.getDomain()); + Array policiesArray = new Array(); + for (Policy policy : domainPolicies.getPolicies()) { + policiesArray.add(asStruct(policy)); + } + appendArray(struct, ATTR_POLICIES, policiesArray); + return struct; + } + + private static Struct asStruct(Policy policy) { + Struct struct = new Struct(); + struct.append(ATTR_NAME, policy.getName()); + struct.append(ATTR_MODIFIED, policy.getModified()); + List assertions = policy.getAssertions(); + if (assertions != null) { + Array assertionsArray = new Array(); + for (Assertion assertion : assertions) { + Struct structAssertion = new Struct(); + appendObject(structAssertion, ATTR_EFFECT, assertion.getEffect()); + structAssertion.append(ATTR_ACTION, assertion.getAction()); + structAssertion.append(ATTR_RESOURCE, assertion.getResource()); + structAssertion.append(ATTR_ROLE, assertion.getRole()); + assertionsArray.add(structAssertion); + } + appendArray(struct, ATTR_ASSERTIONS, assertionsArray); + } + return struct; + } + + private static Struct asStruct(com.yahoo.athenz.zts.Policy policy) { + Struct struct = new Struct(); + struct.append(ATTR_NAME, policy.getName()); + struct.append(ATTR_MODIFIED, policy.getModified()); + List assertions = policy.getAssertions(); + if (assertions != null) { + Array assertionsArray = new Array(); + for (com.yahoo.athenz.zts.Assertion assertion : assertions) { + Struct structAssertion = new Struct(); + appendObject(structAssertion, ATTR_EFFECT, assertion.getEffect()); + structAssertion.append(ATTR_ACTION, assertion.getAction()); + structAssertion.append(ATTR_RESOURCE, assertion.getResource()); + structAssertion.append(ATTR_ROLE, assertion.getRole()); + assertionsArray.add(structAssertion); + } + appendArray(struct, ATTR_ASSERTIONS, assertionsArray); + } + return struct; + } + + private static Struct asStruct(Role role) { + Struct struct = new Struct(); + struct.append(ATTR_NAME, role.getName()); + struct.append(ATTR_MODIFIED, role.getModified()); + appendObject(struct, ATTR_TRUST, role.getTrust()); + appendList(struct, ATTR_MEMBERS, role.getMembers()); + return struct; + } + + private static Struct asStruct(ServiceIdentity service) { + Struct struct = new Struct(); + struct.append(ATTR_NAME, service.getName()); + struct.append(ATTR_MODIFIED, service.getModified()); + appendObject(struct, ATTR_EXECUTABLE, service.getExecutable()); + appendObject(struct, ATTR_GROUP, service.getGroup()); + appendObject(struct, ATTR_PROVIDER_ENDPOINT, service.getProviderEndpoint()); + appendObject(struct, ATTR_USER, service.getUser()); + appendList(struct, ATTR_HOSTS, service.getHosts()); + List publicKeys = service.getPublicKeys(); + if (publicKeys != null) { + Array publicKeysArray = new Array(); + for (PublicKeyEntry publicKey : publicKeys) { + Struct structPublicKey = new Struct(); + structPublicKey.append(ATTR_ID, publicKey.getId()); + structPublicKey.append(ATTR_KEY, publicKey.getKey()); + publicKeysArray.add(structPublicKey); + } + appendArray(struct, ATTR_PUBLIC_KEYS, publicKeysArray); + } + return struct; + } + + private static void appendList(Struct struct, String name, List list) { + if (list == null) { + return; + } + Array items = new Array(); + for (String item : list) { + items.add(item); + } + appendArray(struct, name, items); + } + + private static void appendObject(Struct struct, String name, Object value) { + if (value == null) { + return; + } + if (value instanceof Struct) { + struct.append(name, value); + } else if (value instanceof String) { + struct.append(name, value); + } else { + struct.append(name, value.toString()); + } + } + + private static void appendArray(Struct struct, String name, Array array) { + if (array != null && !array.isEmpty()) { + struct.append(name, array); + } + } + + private static Object asStruct(PolicyData policyData) { + Struct struct = new Struct(); + struct.append(ATTR_DOMAIN, policyData.getDomain()); + List policies = policyData.getPolicies(); + if (policies != null) { + Array policiesArray = new Array(); + for (com.yahoo.athenz.zts.Policy policy : policies) { + policiesArray.add(asStruct(policy)); + } + appendArray(struct, ATTR_POLICIES, policiesArray); + } + return struct; + } + + private static Object asStruct(SignedPolicyData signedPolicyData) { + Struct struct = new Struct(); + struct.append(ATTR_MODIFIED, signedPolicyData.getModified()); + struct.append(ATTR_EXPIRES, signedPolicyData.getExpires()); + struct.append(ATTR_ZMS_KEY_ID, signedPolicyData.getZmsKeyId()); + struct.append(ATTR_ZMS_SIGNATURE, signedPolicyData.getZmsSignature()); + struct.append(ATTR_POLICY_DATA, asStruct(signedPolicyData.getPolicyData())); + return struct; + } + + private static Struct asStruct(DomainData domainData) { + Struct struct = new Struct(); + struct.append(ATTR_MODIFIED, domainData.getModified()); + appendObject(struct, ATTR_ACCOUNT, domainData.getAccount()); + appendObject(struct, ATTR_PRODUCT_ID, domainData.getYpmId()); + if (domainData.getRoles() != null) { + Array structRoles = new Array(); + for (Role role : domainData.getRoles()) { + structRoles.add(asStruct(role)); + } + appendArray(struct, ATTR_ROLES, structRoles); + } + if (domainData.getServices() != null) { + Array structServices = new Array(); + for (ServiceIdentity service : domainData.getServices()) { + structServices.add(asStruct(service)); + } + appendArray(struct, ATTR_SERVICES, structServices); + } + SignedPolicies signedPolicies = domainData.getPolicies(); + if (signedPolicies != null) { + Struct structSignedPolicies = new Struct(); + appendObject(structSignedPolicies, ATTR_SIGNATURE, signedPolicies.getSignature()); + appendObject(structSignedPolicies, ATTR_KEYID, signedPolicies.getKeyId()); + appendObject(structSignedPolicies, ATTR_CONTENTS, asStruct(signedPolicies.getContents())); + struct.append(ATTR_POLICIES, structSignedPolicies); + } + return struct; + } + + private static void appendSeparator(StringBuilder strBuffer) { + // if we have more than a single character + // (which is our initial {/[ character) + // in our buffer then we need to separate + // the item with a comma + if (strBuffer.length() != 1) { + strBuffer.append(','); + } + } + + private static String asCanonicalString(Object obj) { + StringBuilder strBuffer = new StringBuilder(); + if (obj instanceof Struct) { + Struct struct = (Struct) obj; + strBuffer.append('{'); + for (String name : struct.sortedNames()) { + appendSeparator(strBuffer); + strBuffer.append('"'); + strBuffer.append(name); + strBuffer.append("\":"); + strBuffer.append(asCanonicalString(struct.get(name))); + } + strBuffer.append('}'); + } else if (obj instanceof Array) { + strBuffer.append('['); + for (Object item : (Array) obj) { + appendSeparator(strBuffer); + strBuffer.append(asCanonicalString(item)); + } + strBuffer.append(']'); + } else if (obj instanceof String) { + strBuffer.append('"'); + strBuffer.append(obj); + strBuffer.append('"'); + } + return strBuffer.toString(); + } + + public static String asCanonicalString(PolicyData policyData) { + return asCanonicalString(asStruct(policyData)); + } + + public static String asCanonicalString(DomainData domainData) { + return asCanonicalString(asStruct(domainData)); + } + + public static String asCanonicalString(DomainPolicies domainPolicies) { + return asCanonicalString(asStruct(domainPolicies)); + } + + public static String asCanonicalString(SignedPolicyData signedPolicyData) { + return asCanonicalString(asStruct(signedPolicyData)); + } +} diff --git a/libs/java/client_common/src/test/java/com/yahoo/athenz/common/config/AthenzConfigTest.java b/libs/java/client_common/src/test/java/com/yahoo/athenz/common/config/AthenzConfigTest.java new file mode 100644 index 00000000000..a58d1421dac --- /dev/null +++ b/libs/java/client_common/src/test/java/com/yahoo/athenz/common/config/AthenzConfigTest.java @@ -0,0 +1,65 @@ +package com.yahoo.athenz.common.config; + +import static org.testng.Assert.*; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.ArrayList; + +import com.yahoo.athenz.zms.PublicKeyEntry; + +public class AthenzConfigTest { + + AthenzConfig chk_config = new AthenzConfig(); + + @Mock + ArrayList chk_zmsPubkey; + @Mock + ArrayList chk_ztsPubkey; + + @BeforeMethod + public void setUp(){ + MockitoAnnotations.initMocks(this); + } + + @Test + public void testZmsUrl(){ + String chk_zmsUrl = "check_zmsUrl"; + + chk_config.setZmsUrl(chk_zmsUrl); + + String check = chk_config.getZmsUrl(); + assertNotNull(check); + assertEquals(check,"check_zmsUrl"); + } + + @Test + public void testZtsUrl(){ + String chk_ztsUrl = "check_ztsUrl"; + + chk_config.setZtsUrl(chk_ztsUrl); + + String check = chk_config.getZtsUrl(); + assertNotNull(check); + assertEquals(check,"check_ztsUrl"); + } + + @Test + public void testZmsPrivateKey() { + chk_config.setZmsPublicKeys(chk_zmsPubkey); + + ArrayList check = chk_config.getZmsPublicKeys(); + assertNotNull(check); + } + + @Test + public void testZtsPrivateKey() { + chk_config.setZtsPublicKeys(chk_ztsPubkey); + + ArrayList check = chk_config.getZtsPublicKeys(); + assertNotNull(check); + } +} diff --git a/libs/java/client_common/src/test/java/com/yahoo/athenz/common/metrics/impl/MetricsTest.java b/libs/java/client_common/src/test/java/com/yahoo/athenz/common/metrics/impl/MetricsTest.java new file mode 100644 index 00000000000..7c6f519282d --- /dev/null +++ b/libs/java/client_common/src/test/java/com/yahoo/athenz/common/metrics/impl/MetricsTest.java @@ -0,0 +1,35 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.metrics.impl; + +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.metrics.MetricFactory; +import com.yahoo.athenz.common.metrics.impl.NoOpMetricFactory; + +public class MetricsTest { + + @Test + public void testFactoryNoOpMetric() throws Exception { + + MetricFactory factory = new NoOpMetricFactory(); + Metric metric = factory.create(); + + assertEquals(metric.getClass().getName(), Class.forName("com.yahoo.athenz.common.metrics.impl.NoOpMetric").getName()); + } +} diff --git a/libs/java/client_common/src/test/java/com/yahoo/athenz/common/utils/SignUtilsTest.java b/libs/java/client_common/src/test/java/com/yahoo/athenz/common/utils/SignUtilsTest.java new file mode 100644 index 00000000000..3af75d2935a --- /dev/null +++ b/libs/java/client_common/src/test/java/com/yahoo/athenz/common/utils/SignUtilsTest.java @@ -0,0 +1,165 @@ +package com.yahoo.athenz.common.utils; + +import static org.testng.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.DomainData; +import com.yahoo.athenz.zms.DomainPolicies; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.PublicKeyEntry; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.ServiceIdentity; +import com.yahoo.athenz.zms.SignedPolicies; +import com.yahoo.athenz.zts.PolicyData; +import com.yahoo.athenz.zts.SignedPolicyData; + +public class SignUtilsTest { + + @Mock + PolicyData mockPolicy; + @Mock + DomainData mockDomain; + @Mock + DomainPolicies mockPolicies; + @Mock + SignedPolicyData mockSignedPolicy; + + SignUtils chk_utils = new SignUtils(); + + @BeforeMethod + public void setUp(){ + MockitoAnnotations.initMocks(this); + } + + @Test + public void testAsCanonicalStringPolicyData() { + Mockito.when(mockPolicy.getPolicies()).thenReturn(null); + + String check = SignUtils.asCanonicalString(mockPolicy); + assertNotNull(check); + assertEquals(check,"{\"domain\":}"); + } + + @Test + public void testAsCanonicalStringDomainData() { + Mockito.when(mockDomain.getRoles()).thenReturn(null); + Mockito.when(mockDomain.getServices()).thenReturn(null); + + String check = SignUtils.asCanonicalString(mockDomain); + assertNotNull(check); + assertEquals(check,"{\"modified\":,\"ypmId\":\"0\"}"); + } + + @Test + public void testAsCanonicalStringDomainPolicies() { + String check = SignUtils.asCanonicalString(mockPolicies); + assertNotNull(check); + assertEquals(check,"{\"domain\":}"); + } + + @Test + public void testAsCanonicalStringSignedPolicyData() { + Mockito.when(mockSignedPolicy.getPolicyData()).thenReturn(mockPolicy); + + String check = SignUtils.asCanonicalString(mockSignedPolicy); + assertNotNull(check); + assertEquals(check,"{\"expires\":,\"modified\":,\"policyData\":{\"domain\":},\"zmsKeyId\":,\"zmsSignature\":}"); + } + + @Test + public void testAsStructPolicy() { + List policies = new ArrayList(); + Policy mPolicy = Mockito.mock(Policy.class); + policies.add(mPolicy); + + List assertions = new ArrayList(); + Assertion mAssertion = Mockito.mock(Assertion.class); + assertions.add(mAssertion); + + Mockito.when(mockPolicies.getPolicies()).thenReturn(policies); + Mockito.when(mPolicy.getAssertions()).thenReturn(assertions); + + String check = SignUtils.asCanonicalString(mockPolicies); + assertNotNull(check); + assertEquals(check,"{\"domain\":,\"policies\":[{\"assertions\":[{\"action\":,\"resource\":,\"role\":}],\"modified\":,\"name\":}]}"); + + Mockito.when(mPolicy.getAssertions()).thenReturn(null); + + check = SignUtils.asCanonicalString(mockPolicies); + assertNotNull(check); + assertEquals(check,"{\"domain\":,\"policies\":[{\"modified\":,\"name\":}]}"); + } + + @Test + public void testAsStructZTSPolicy() { + List policies = new ArrayList(); + com.yahoo.athenz.zts.Policy mPolicy = Mockito.mock(com.yahoo.athenz.zts.Policy.class); + policies.add(mPolicy); + + List assertions = new ArrayList(); + com.yahoo.athenz.zts.Assertion mAssertion = Mockito.mock(com.yahoo.athenz.zts.Assertion.class); + assertions.add(mAssertion); + + Mockito.when(mockPolicy.getPolicies()).thenReturn(policies); + Mockito.when(mPolicy.getAssertions()).thenReturn(assertions); + + String check = SignUtils.asCanonicalString(mockPolicy); + assertNotNull(check); + assertEquals(check,"{\"domain\":,\"policies\":[{\"assertions\":[{\"action\":,\"resource\":,\"role\":}],\"modified\":,\"name\":}]}"); + + Mockito.when(mPolicy.getAssertions()).thenReturn(null); + + check = SignUtils.asCanonicalString(mockPolicy); + assertNotNull(check); + assertEquals(check,"{\"domain\":,\"policies\":[{\"modified\":,\"name\":}]}"); + } + + @Test + public void testAsStructRoleService() { + List roles = new ArrayList(); + Role mRole = Mockito.mock(Role.class); + roles.add(mRole); + + List items = new ArrayList(); + String item = "check_item"; + items.add(item); + + List services = new ArrayList(); + ServiceIdentity mService = Mockito.mock(ServiceIdentity.class); + services.add(mService); + + List publicKeys = new ArrayList(); + PublicKeyEntry mPublicKey = Mockito.mock(PublicKeyEntry.class); + publicKeys.add(mPublicKey); + + SignedPolicies signedPolicies = Mockito.mock(SignedPolicies.class); + + Mockito.when(mockDomain.getAccount()).thenReturn("chk_string"); + Mockito.when(mockDomain.getRoles()).thenReturn(roles); + Mockito.when(mRole.getMembers()).thenReturn(items); + Mockito.when(mockDomain.getServices()).thenReturn(services); + Mockito.when(mService.getHosts()).thenReturn(null); + Mockito.when(mService.getPublicKeys()).thenReturn(publicKeys); + Mockito.when(mockDomain.getPolicies()).thenReturn(signedPolicies); + Mockito.when(signedPolicies.getContents()).thenReturn(mockPolicies); + + String check = SignUtils.asCanonicalString(mockDomain); + assertNotNull(check); + assertEquals(check,"{\"account\":\"chk_string\",\"modified\":,\"policies\":{\"contents\":{\"domain\":}},\"roles\":[{\"members\":[\"check_item\"],\"modified\":,\"name\":}],\"services\":[{\"modified\":,\"name\":,\"publicKeys\":[{\"id\":,\"key\":}]}],\"ypmId\":\"0\"}"); + + Mockito.when(mService.getPublicKeys()).thenReturn(null); + + check = SignUtils.asCanonicalString(mockDomain); + assertNotNull(check); + assertEquals(check,"{\"account\":\"chk_string\",\"modified\":,\"policies\":{\"contents\":{\"domain\":}},\"roles\":[{\"members\":[\"check_item\"],\"modified\":,\"name\":}],\"services\":[{\"modified\":,\"name\":}],\"ypmId\":\"0\"}"); + } +} diff --git a/libs/java/client_common/src/test/resources/logback.xml b/libs/java/client_common/src/test/resources/logback.xml new file mode 100644 index 00000000000..c4a0357b8d5 --- /dev/null +++ b/libs/java/client_common/src/test/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + System.out + + %-4relative [%thread] %-5level %class - %msg%n + + + + + + + + + + diff --git a/libs/java/server_common/README.md b/libs/java/server_common/README.md new file mode 100644 index 00000000000..ccdd05a3f5f --- /dev/null +++ b/libs/java/server_common/README.md @@ -0,0 +1,15 @@ +Athenz Server Common Classes +============================ + +Common classes used throughout Athenz Server components. + +- logging classes: access, audit, et al +- filter classes: health and etag +- debug authorities (Kerberos, Principal, and Role) used by both ZMS and ZTS Servers. +- utils: StringUtils + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/libs/java/server_common/pom.xml b/libs/java/server_common/pom.xml new file mode 100644 index 00000000000..7b7bd5dde6a --- /dev/null +++ b/libs/java/server_common/pom.xml @@ -0,0 +1,108 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../../pom.xml + + + server_common + server_common + Athenz Server Common Packages + jar + + + + ${project.groupId} + auth_core + ${project.parent.version} + + + org.glassfish.jersey.containers + jersey-container-jdk-http + ${jersey.version} + true + + + org.glassfish.jersey.containers + jersey-container-servlet + ${jersey.version} + true + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey.version} + + + com.fasterxml.jackson.core + jackson-annotations + + + + + org.glassfish.jersey.core + jersey-client + ${jersey.version} + + + org.glassfish.jersey.core + jersey-common + ${jersey.version} + + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + + + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + + + + + org.eclipse.jetty + jetty-servlet + ${jetty.version} + + + + diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugKerberosAuthority.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugKerberosAuthority.java new file mode 100644 index 00000000000..a2cb7513877 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugKerberosAuthority.java @@ -0,0 +1,82 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.debug; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.SimplePrincipal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An example com.yahoo.rest.Authority implementation that uses Kerberos ticket without real validation. + * This makes it easy to fake tickets for arbitrary users to test many different users. + * THIS IS FOR TESTING ONLY + */ +public class DebugKerberosAuthority implements Authority { + + private static final Logger LOG = LoggerFactory.getLogger(DebugKerberosAuthority.class); + + static final String KRB_HEADER = "Authorization"; + static final String TOKEN_PREFIX = "Negotiate"; + static final String USER_DOMAIN = "user"; + + // for setting default debug user name to be used in returned Principals + public static final String ATHENZ_PROP_USER_NAME = "athenz.common.server.debug.krb_tkt_user_name"; + public static final String ATHENZ_PROP_USER_DOMAIN = "athenz.user_domain"; + + // if Authorization header contains fake ticket with debug field, then use + // the suffix as the user name + // ex: "Negotiate debug:jamesdean" + // The user name returned in the principal would be "jamesdean" + static final String TOKEN_DEBUG_USER_FIELD = "debug:"; + + String defaultUserName = "anonymous"; + String userDomain = USER_DOMAIN; + + public String getDomain() { + return userDomain; + } + + public String getHeader() { + return KRB_HEADER; + }; + + public Principal authenticate(String creds, String remoteAddr, String httpMethod, StringBuilder errMsg) { + String uname = defaultUserName; + if (creds == null) { + LOG.debug("DebugKerberosAuthority:authenticate: Missing ticket"); + return null; + } else if (creds.startsWith(TOKEN_PREFIX) == false) { + LOG.debug("DebugKerberosAuthority:authenticate: bad format: Missing prefix=" + TOKEN_PREFIX + " in ticket=" + creds); + return null; + } else { + creds = creds.substring(TOKEN_PREFIX.length()).trim(); + if (creds.startsWith(TOKEN_DEBUG_USER_FIELD)) { + uname = creds.substring(TOKEN_DEBUG_USER_FIELD.length()).trim(); + } + } + return SimplePrincipal.create(getDomain(), uname, creds, this); + } + + @Override + public void initialize() { + defaultUserName = System.getProperty(ATHENZ_PROP_USER_NAME, "anonymous"); + userDomain = System.getProperty(ATHENZ_PROP_USER_DOMAIN, USER_DOMAIN); + } +} + diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugPrincipalAuthority.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugPrincipalAuthority.java new file mode 100644 index 00000000000..4d2dcda23ad --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugPrincipalAuthority.java @@ -0,0 +1,94 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.debug; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.AuthorityKeyStore; +import com.yahoo.athenz.auth.KeyStore; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.PrincipalAuthority; +import com.yahoo.athenz.auth.impl.SimplePrincipal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//this class does not verify the signature. +//to do that, it would need to get the public key from ZMS +public class DebugPrincipalAuthority implements Authority, AuthorityKeyStore { + + private static final Logger LOG = LoggerFactory.getLogger(DebugPrincipalAuthority.class); + private final String headerName = System.getProperty(PrincipalAuthority.ATHENZ_PROP_PRINCIPAL_HEADER, PrincipalAuthority.HTTP_HEADER); + + public String getDomain() { + return null; //services *are* a domain + } + + public String getHeader() { + return headerName; + } + + public Principal authenticate(String nToken, String remoteAddr, String httpMethod, StringBuilder errMsg) { + + if (nToken == null) { + if (LOG.isInfoEnabled()) { + LOG.info("Token was not specified for authentication"); + } + + return null; + } + + if (LOG.isInfoEnabled()) { + LOG.info("Principal Authority: authenticating token: " + nToken); + } + + String domainName = null; + String serviceName = null; + if (nToken.indexOf(';') > 0) { + for (String item : nToken.split(";")) { + String [] kv = item.split("="); + if (kv.length == 2) { + if ("d".equals(kv[0])) { + domainName = kv[1]; + } else if ("n".equals(kv[0])) { + serviceName = kv[1]; + } + } + } + } + + if (domainName == null || serviceName == null) { + if (LOG.isInfoEnabled()) { + LOG.info("Unable to extract domain or service names"); + } + + return null; + } + String fullName = domainName + "." + serviceName; + if (LOG.isInfoEnabled()) { + LOG.info("[debug-authenticated: " + fullName + "]"); + } + + return SimplePrincipal.create(domainName, serviceName, nToken, 0, this); + } + + @Override + public void initialize() { + } + + @Override + public void setKeyStore(KeyStore keyStore) { + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugRoleAuthority.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugRoleAuthority.java new file mode 100644 index 00000000000..8943b43ef5c --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugRoleAuthority.java @@ -0,0 +1,92 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.debug; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.AuthorityKeyStore; +import com.yahoo.athenz.auth.KeyStore; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.RoleAuthority; +import com.yahoo.athenz.auth.impl.SimplePrincipal; + +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//this class does not verify the signature or the expiration times, +//just extracts what it needs for debugging purposes. +public class DebugRoleAuthority implements Authority, AuthorityKeyStore { + + private static final Logger LOG = LoggerFactory.getLogger(DebugRoleAuthority.class); + private final String headerName = System.getProperty(RoleAuthority.ATHENZ_PROP_ROLE_HEADER, RoleAuthority.HTTP_HEADER); + + public String getDomain() { + return null; //the domain is part of the principal for roles + } + + public String getHeader() { + return headerName; + } + + public Principal authenticate(String zToken, String remoteAddr, String httpMethod, StringBuilder errMsg) { + if (zToken == null) { + return null; + } + String domainName = null; + String roleNames = null; + String version = null; + if (zToken.indexOf(';') > 0) { + for (String item : zToken.split(";")) { + String [] kv = item.split("="); + if (kv.length == 2) { + if ("d".equals(kv[0])) { + domainName = kv[1]; + } else if ("r".equals(kv[0])) { + roleNames = kv[1]; + } else if ("v".equals(kv[0])) { + version = kv[1]; + } + } + } + } + if (!"Z1".equals(version)) { + return null; + } + if (domainName == null || roleNames == null) { + return null; + } + + //Expiration is not checked in this debugging class. + + List roles = Arrays.asList(roleNames.split(",")); + Principal p = SimplePrincipal.create(domainName, zToken, roles, this); + if (LOG.isInfoEnabled()) { + LOG.info("[debug-authenticated: '" + p + "']"); + } + + return p; + } + + @Override + public void initialize() { + } + + @Override + public void setKeyStore(KeyStore keyStore) { + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugUserAuthority.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugUserAuthority.java new file mode 100644 index 00000000000..a419402210c --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/debug/DebugUserAuthority.java @@ -0,0 +1,102 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.debug; + +import java.nio.charset.StandardCharsets; + +import org.bouncycastle.util.encoders.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.SimplePrincipal; + +/** + * Implementation that performs validation of PAM. + */ +public class DebugUserAuthority implements Authority { + + private static final Logger LOG = LoggerFactory.getLogger(DebugUserAuthority.class); + + public DebugUserAuthority() { + } + + @Override + public void initialize() { + } + + @Override + public String getDomain() { + return "user"; + } + + @Override + public String getHeader() { + return "Authorization"; + }; + + /* + * we don't want the user to keep specifying their username and + * password as part of the request. instead, the user must first + * request a usertoken and then use that usertoken for all other + * requests against ZMS and ZTS servers. + * @see com.yahoo.athenz.auth.Authority#allowAuthorization() + */ + @Override + public boolean allowAuthorization() { + return false; + } + + @Override + public Principal authenticate(String creds, String remoteAddr, String httpMethod, StringBuilder errMsg) { + errMsg = errMsg == null ? new StringBuilder(512) : errMsg; + + // the HTTP Basic authorization format is: Basic base64(:) + + if (!creds.startsWith("Basic ")) { + errMsg.append("UserAuthority:authenticate: credentials do not start with 'Basic '"); + LOG.error(errMsg.toString()); + return null; + } + + // decode - need to skip the first 6 bytes for 'Basic ' + String decoded; + try { + decoded = new String(Base64.decode(creds.substring(6).getBytes(StandardCharsets.UTF_8))); + } catch (Exception e) { + errMsg.append("UserAuthority:authenticate: factory exc="); + LOG.error(errMsg.toString()); + return null; + } + + String[] userArray = decoded.split(":"); + String username = userArray[0]; + + if (LOG.isDebugEnabled()) { + LOG.debug("UserAuthority.authenticate: valid user=" + username); + } + + // all the role members in Athenz are normalized to lower case so we need to make + // sure our principal's name and domain are created with lower case as well + + long issueTime = 0; + SimplePrincipal princ = (SimplePrincipal) SimplePrincipal.create(getDomain().toLowerCase(), + userArray[0].toLowerCase(), creds, issueTime, this); + princ.setUnsignedCreds(creds); + return princ; + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/filters/DefaultMediaTypeFilter.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/filters/DefaultMediaTypeFilter.java new file mode 100644 index 00000000000..335564e42a3 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/filters/DefaultMediaTypeFilter.java @@ -0,0 +1,45 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.filters; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.PreMatching; + +import static javax.ws.rs.core.HttpHeaders.ACCEPT; + +/* + * If requestor specified any type Accepted, this will reset it to a + * predictable default of application/json + */ + +@PreMatching +public class DefaultMediaTypeFilter implements ContainerRequestFilter { + + final static String MEDIA_TYPE_ANY = "*/*"; + final static String MEDIA_TYPE_JSON = "application/json"; + + @Override + public void filter(ContainerRequestContext reqCtx) { + + String acceptHdr = reqCtx.getHeaderString(ACCEPT); + if (acceptHdr == null || acceptHdr.contains(MEDIA_TYPE_ANY)) { + // replace it with JSON + javax.ws.rs.core.MultivaluedMap headers = reqCtx.getHeaders(); + headers.putSingle(ACCEPT, MEDIA_TYPE_JSON); + } + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/filters/ETagFilter.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/filters/ETagFilter.java new file mode 100644 index 00000000000..8c8089684a0 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/filters/ETagFilter.java @@ -0,0 +1,66 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.filters; + +import javax.ws.rs.core.Context; +import javax.ws.rs.core.EntityTag; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.ext.Provider; + +import com.yahoo.athenz.common.server.util.StringUtils; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; + +@Provider +public class ETagFilter implements ContainerResponseFilter { + + @Context private javax.servlet.http.HttpServletResponse servletResponse; + + @Override + public void filter(ContainerRequestContext request, ContainerResponseContext response) { + + // if our response object has a string ETag header value we're going + // to replace it with its corresponding EntityTag object since the + // the jersey GZIPResponse handler expects that object. + + // We could be either setting the in the container response (this is when + // we're returning not-modified response since we're creating the container + // response directly) or in the servlet response (this is where we're just + // returning our data set with the header included). + + // So we'll check in the container response first and if we don't have + // anything there we'll check in the servlet response. If we find the + // header in the servlet response, we're going to remove it and set a new + // entity tag object in the container response + + String etagStr = null; + if (response.getHeaders().containsKey(HttpHeaders.ETAG)) { + etagStr = (String) response.getHeaders().getFirst(HttpHeaders.ETAG); + } + if (etagStr == null && servletResponse != null) { + etagStr = servletResponse.getHeader(HttpHeaders.ETAG); + if (etagStr != null) { + servletResponse.setHeader(HttpHeaders.ETAG, null); + } + } + if (etagStr != null) { + etagStr = StringUtils.removeLeadingAndTrailingQuotes(etagStr); + response.getHeaders().putSingle("ETag", new EntityTag(etagStr)); + } + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AthenzRequestLog.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AthenzRequestLog.java new file mode 100644 index 00000000000..dce039712fb --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AthenzRequestLog.java @@ -0,0 +1,133 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.log; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jetty.server.NCSARequestLog; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +import com.yahoo.athenz.common.server.util.ServletRequestUtil; + +public class AthenzRequestLog extends NCSARequestLog { + + protected static final Logger LOG = Log.getLogger(AthenzRequestLog.class); + + static final String AUDIT_CRED_ERROR_ATTR = "com.yahoo.athenz.auth.credential.error"; + static final String AUDIT_LOG_CRED_REF = "Authority Credential Error: Request Access Log"; + + static final Pattern AUDIT_CRED_ERROR_PAT_WHO = Pattern.compile(".*(credential=)(.*)"); + + private AuditLogger auditLogger = null; + + public AthenzRequestLog() { + } + + public AthenzRequestLog(String filename) { + this(filename, null); + } + + public AthenzRequestLog(String filename, AuditLogger auditLogger) { + super(filename); + this.auditLogger = auditLogger; + } + + /** + * Helper method useful for over-riding in test cases. + */ + protected Logger getBackupLogger() { + return LOG; + } + + @Override + public void log(final Request request, final Response response) { + + // first log our standard access entry + + super.log(request, response); + + // if this was a request that failed authentication and + // was never got to be processed by the server, let's + // add to our audit log before returning + + AuditLogMsgBuilder msgBldr = getAuditLogMsgBuilder(request); + if (msgBldr != null) { + if (auditLogger == null) { + getBackupLogger().warn(msgBldr.build()); + } else { + auditLogger.log(msgBldr); + } + } + } + + /* + * Parse authenticate credential error message for token string. + * If found, return the token string, else null. + */ + static String getWhoFromErrorMsg(String errMsg) { + // look for "credential=" + CharSequence charSeq = errMsg.subSequence(0, errMsg.length()); + Matcher pm = AUDIT_CRED_ERROR_PAT_WHO.matcher(charSeq); + if (pm.matches() && pm.groupCount() >= 2) { + // we want group number 2, see AUDIT_CRED_ERROR_PAT_WHO for defined groups + return pm.group(2); + } + return null; + } + + AuditLogMsgBuilder getAuditLogMsgBuilder(final Request request) { + Object credErr = request.getAttribute(AUDIT_CRED_ERROR_ATTR); + if (credErr == null) { + return null; + } + + AuditLogMsgBuilder msgBldr = AuditLogFactory.getMsgBuilder(); + String httpMethod = request.getMethod(); + msgBldr.whatMethod(httpMethod); + + String details = credErr.toString(); + String who = getWhoFromErrorMsg(details); + if (who != null) { + msgBldr.who(who); + } else { + msgBldr.who("UNKNOWN principal: please see WHAT details"); + } + + msgBldr.why(AUDIT_LOG_CRED_REF); + msgBldr.whatApi(request.getRequestURI()); + + msgBldr.when(ServletRequestUtil.getTimeStamp(request)); + msgBldr.clientIp(ServletRequestUtil.getRemoteAddress(request)); + + msgBldr.whereIp(request.getLocalName()); + msgBldr.whereHttpsPort(Integer.toString(request.getServerPort())); + msgBldr.whatEntity(AUDIT_CRED_ERROR_ATTR); + + StringBuilder sb = new StringBuilder(512); + sb.append("CRED_ERROR=(").append(details).append(");"); + String remoteUser = request.getRemoteUser(); + if (remoteUser != null) { + sb.append("REMOTEUSER=(").append(remoteUser).append(");"); + } + + msgBldr.whatDetails(sb.toString()); + return msgBldr; + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogFactory.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogFactory.java new file mode 100644 index 00000000000..753fef70807 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogFactory.java @@ -0,0 +1,96 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.log; + +import com.yahoo.athenz.common.server.log.impl.DefaultAuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.impl.DefaultAuditLogger; + +/** + * Factory to produce Audit logging components. + */ +public class AuditLogFactory { + + /** + * Get the default AuditLogger implementation. + * @return default AuditLogger instance + */ + static public AuditLogger getLogger() { + return new DefaultAuditLogger(); + } + + /** + * Create the AuditLogger from the given class name using its default constructor. + * If the class name is null, then it returns the default AuditLogger. + * @param auditLoggerClassName is name of class to instantiate as the AuditLogger + * @return AuditLogger instance + * @throws Exception class instantiation errors, ie. ReflectiveOperationException + */ + static public AuditLogger getLogger(String auditLoggerClassName) throws Exception { + if (auditLoggerClassName == null) { + return getLogger(); + } + + AuditLogger impl = null; + impl = (AuditLogger) Class.forName(auditLoggerClassName).newInstance(); + + return impl; + } + + /** + * Create the AuditLogger from the given class name and provided parameter, using + * the classes constructor that will take the parameter. + * ex: AuditLogger logger = AuditLogFactory.getLogger("my_logger_class", new Integer(5)); + * @param auditLoggerClassName is name of class to instantiate as the AuditLogger + * @param param is a parameter passed to the constructor of the specified Logger class + * @return AuditLogger instance + * @throws Exception class instantiation errors, ie. ReflectiveOperationException + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + static public AuditLogger getLogger(String auditLoggerClassName, Object param) throws Exception { + if (param == null) { + return getLogger(auditLoggerClassName); + } + Class paramClass = param.getClass(); + Class loggerClass = Class.forName(auditLoggerClassName); + return (AuditLogger) loggerClass.getConstructor(new Class[] {paramClass}).newInstance(paramClass.cast(param)); + } + + /** + * Get the default AuditLogMsgBuilder implementation. + * @return default AuditLogMsgBuilder instance + */ + static public AuditLogMsgBuilder getMsgBuilder() { + return new DefaultAuditLogMsgBuilder(); + } + + /** + * Create the AuditLogMsgBuilder from the given class name using its default constructor. + * If the class name is null, return the default AuditLogMsgBuilder. + * @param auditLogMsgBuilderClassName is name of class to instantiate as an AuditLogMsgBuilder + * @return AuditLogMsgBuilder instance + * @throws Exception class instantiation errors, ie. ReflectiveOperationException + */ + static public AuditLogMsgBuilder getMsgBuilder(String auditLogMsgBuilderClassName) throws Exception { + if (auditLogMsgBuilderClassName == null) { + return getMsgBuilder(); + } + + AuditLogMsgBuilder impl = null; + impl = (AuditLogMsgBuilder) Class.forName(auditLogMsgBuilderClassName).newInstance(); + + return impl; + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogMsgBuilder.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogMsgBuilder.java new file mode 100644 index 00000000000..994fe45fe14 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogMsgBuilder.java @@ -0,0 +1,166 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.log; + +import com.yahoo.rdl.Struct; +import com.yahoo.rdl.Timestamp; + +public interface AuditLogMsgBuilder { + + /** + * Return a tag with the version of the msg builder used to build the message. + * Ex: "VERS(athenz-2.1);" + * @return version tag all ready to set in a log message + */ + public abstract String versionTag(); + + /** + * who made the authorization change + * @param whoVal typically contains a token (user, role) + * The calling client/user requesting the change. + * @return this + */ + public abstract AuditLogMsgBuilder who(String whoVal); + + public abstract String who(); + + /** + * why was this change requested - justification via SOX ticket number + * @param whyVal typically a ticket number or some identifier for reference + * The SOX ticket number. + * @return this + */ + public abstract AuditLogMsgBuilder why(String whyVal); + + public abstract String why(); + + public abstract AuditLogMsgBuilder when(Timestamp ts); + + public abstract AuditLogMsgBuilder when(String whenVal); + + public abstract String when(); + + /** + * IP address of requesting client + * @param clientIpAddr address of the calling client + * The IP address of the calling client(who). + * @return this + */ + public abstract AuditLogMsgBuilder clientIp(String clientIpAddr); + + public abstract String clientIp(); + + /** + * This is where the change request was received - server endpoint. + * @param whereVal is the server address where the request was received + * The IP address and ports of the server where it receives the + * requests at. + * Ex: '{"server-ip":"198.177.62.9","server-https-port":"4453","server-http-port":"10080"}' + * @return this + */ + public abstract AuditLogMsgBuilder whereIp(String whereVal); + + public abstract AuditLogMsgBuilder whereHttpsPort(String whereVal); + + public abstract AuditLogMsgBuilder whereHttpPort(String whereVal); + + // Ex: '{"server-ip":"198.177.62.9","server-https-port":"4453","server-http-port":"10080"}' + public abstract String where(); + + /** + * The REST methods required to be reported are PUT, POST, DELETE. + * @param whatMethodVal is the typical REST method + * This is the REST method, ie. "PUT" or "POST", etc + * @return this + */ + public abstract AuditLogMsgBuilder whatMethod(String whatMethodVal); + + public abstract String whatMethod(); + + /** + * The publicly exported API receiving the change request. + * @param whatApiVal names the API that received the request + * This is the server public method serving the request, ex: "putRole" + * @return this + */ + public abstract AuditLogMsgBuilder whatApi(String whatApiVal); + + public abstract String whatApi(); + + /** + * Name of the domain that is affected by the change. + * @param whatDomainVal is the name of the domain being affected + * This is the Athenz domain being changed, ex: "xobni" + * @return this + */ + public abstract AuditLogMsgBuilder whatDomain(String whatDomainVal); + + public abstract String whatDomain(); + + /** + * Name of the entity being changed. An entity is a policy, role, service, et al. + * @param whatEntityVal is the name of the particular entity + * This is the entity in the Athenz domain being changed. + * So for example, if the role called "admin" is changed, then entity is "admin". + * @return this + */ + public abstract AuditLogMsgBuilder whatEntity(String whatEntityVal); + + public abstract String whatEntity(); + + /** + * @param whatDetailsVal specific details of the changes + * The caller will specify the entity(whatEntity) that was + * changed. If the entity is a role, and members were added, then + * the details will specify that members were added. + * Ex: '{"members-removed":["user.manning"],"members-added"=["user.brady"]}' + * See the whatDetails() helper that takes Set's. It can be used with + * method to sort out the changes to enhance the details message. + * Usage can be: + * Builder bldr = bldr.whatDetails("members-changed"); + * bldr = bldr.whatDetails(oldAttrSet, newAttrSet); + * @return this + */ + public abstract AuditLogMsgBuilder whatDetails(String whatDetailsVal); + + public abstract String whatDetails(); + + /** + * Performs 'diff' of the attributes and sets it in the details part of the + * message accordingly. + * This method will sort out what attributes have been REMOVED, and which have + * been ADDED based on the old original attributes and the new set of attrs. + * @param tag used to label the details + * @param origFields Set of current attributes + * @param newFields Set of replacement attributes + * @return this + */ + public abstract AuditLogMsgBuilder whatDetails(String tag, + Struct origFields, Struct newFields); + + /** + * Call this to build the string representation of the data fields set herein. + * @return String representation of the message to be logged + */ + public abstract String build(); + + /** + * Parse the given log message and return a Struct. + * @param logMsgBldrMsg string returned from AuditLogMsgBuilder.build() + * @return struct representation of the log message + */ + public abstract Struct parse(String logMsgBldrMsg); +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogger.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogger.java new file mode 100644 index 00000000000..f02288ae2fa --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/AuditLogger.java @@ -0,0 +1,38 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.log; + +/** + * Interface to perform audit logging. + * See {@link com.yahoo.athenz.common.server.log.AuditLogFactory#getLogger()} + * + */ +public interface AuditLogger { + /** + * Perform logging of the given message. + * @param logMsg message to be logged + * @param msgVersionTag optional version tag of the message - may be null + * If the message must be split into chunks then msgVersionTag + * will be used prefixed to each chunk/partition. + */ + public void log(String logMsg, String msgVersionTag); + + /** + * Log the message as built by the provided msgBldr. + * @param msgBldr constructs message to be logged, contains version tag of the message + */ + public void log(AuditLogMsgBuilder msgBldr); +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/impl/DefaultAuditLogMsgBuilder.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/impl/DefaultAuditLogMsgBuilder.java new file mode 100644 index 00000000000..5d8cffb12e0 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/impl/DefaultAuditLogMsgBuilder.java @@ -0,0 +1,683 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.log.impl; + +import java.util.Set; +import java.util.HashSet; +import java.util.Iterator; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +import com.yahoo.rdl.Struct; +import com.yahoo.rdl.Timestamp; +import com.yahoo.rdl.Value; +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.rdl.Array; + +/* + * Default implementation that can be inherited from. + * Builds the Audit logging message to be passed to the AuditLog interface. + * The log message built by this class can be parsed into a Struct (see parse()). + * Example string built: + * VERS=(athenz-def-1.0);WHEN=(2015-04-02T18:30:58.441Z);WHO=(v=U1;d=user;n=jdoe); + * WHY=(audittest);WHERE=(server-ip=localhost,server-https-port=0,server-http-port=10080); + * CLIENT-IP=(MOCKCLIENT_HOST_NAME);WHAT-method=(PUT);WHAT-api=(puttenancy); + * WHAT-domain=(AddTenancyDom1);WHAT-entity=(tenancy.coretech.storage.reader); + * WHAT-details=({assertions: [{role: "AddTenancyDom1:role.admin", action: "ASSUME_ROLE", resource: "coretech:role.storage.tenant.AddTenancyDom1.reader"}], modified: "2015-04-02T18:30:58.441Z", name: "AddTenancyDom1:policy.tenancy.coretech.storage.reader"}); + */ +public class DefaultAuditLogMsgBuilder implements AuditLogMsgBuilder { + + // Keys used in Struct returned by parse() method + // + // These keys will contain a String value + public static final String PARSE_VERS = "VERS"; + public static final String PARSE_WHEN = "WHEN"; + public static final String PARSE_WHO = "WHO"; + public static final String PARSE_WHY = "WHY"; + public static final String PARSE_WHERE = "WHERE"; + public static final String PARSE_CLIENT_IP = "CLIENT-IP"; + public static final String PARSE_WHAT_METH = "WHAT-method"; + public static final String PARSE_WHAT_API = "WHAT-api"; + public static final String PARSE_WHAT_DOM = "WHAT-domain"; + public static final String PARSE_WHAT_ENT = "WHAT-entity"; + + // This key will contain a Struct value + public static final String PARSE_WHAT_DETAILS = "WHAT-details"; + + // These fields are found in the embedded Struct keyed by "WHAT-details" + // + // These fields contain a String + public static final String PARSE_DETAILS_ADDED = "ADDED"; + public static final String PARSE_DETAILS_REMOVED = "REMOVED"; + public static final String PARSE_DETAILS_CHANGED = "CHANGED"; + // + // Each of these fields is an Array of Strings. + // + public static final String PARSE_DETAILS_ADDEDVALS = "ADDED-VALUES"; + public static final String PARSE_DETAILS_REMOVEDVALS = "REMOVED-VALUES"; + public static final String PARSE_DETAILS_FROMTOVALS = "FROM-TO-VALUES"; + public static final String PARSE_DETAILS_EMB_ADDEDVALS = "EMBEDDED-ADDED"; + public static final String PARSE_DETAILS_EMB_REMOVEDVALS = "EMBEDDED-REMOVED"; + + // log message contains these fields - which parse combines into one field + // + public static final String PARSE_FROM = "FROM"; + public static final String PARSE_TO = "TO"; + + // data members used for log message fields + // + protected String who = null; // The calling client/user requesting the change. Ex: "user.roger" + protected String why = null; // The audit reference or SOX ticket number. + protected String clientIp = null; // The IP address of the calling client(who). + protected String when = null; // date-time in UTC + protected String whereIp = null; // The IP address and ports of the server where it receives the requests at. + protected String whereHttpsPort = null; // The server https port endpoint + protected String whereHttpPort = null; // The server http port endpoint + protected String whatMethod = null; // This is the REST method, ie. "PUT" or "POST", etc + protected String whatApi = null; // This is the server public method serving the request, ex: "putRole" + protected String whatDomain = null; // This is the Athenz domain being changed, ex: "xobni" + protected String whatEntity = null; // This is the entity in the Athenz domain being changed. + protected String whatDetails = null; // This will contain specifics of the entity(whatEntity) that was changed. + + // Version of the log message produced. In case newer versions of the message creation + // are implemented later. + // + protected String messageVersion = "athenz-def-1.0"; + protected String versionTag = null; + + static final String NULL_STR = "null"; // place holder key word used for missing values + static final int SB_MIN_SIZE_INIT = 128; + static final int SB_MED_SIZE_INIT = 512; + static final int SB_MAX_SIZE_INIT = 1024; + + // setup for parsing log messages + // + static final String GEN_FLD_PAT = "=\\(([^\\)]+)\\);.*"; + static final Pattern PAT_VERS = Pattern.compile(".*(" + PARSE_VERS + ")" + GEN_FLD_PAT); + static final Pattern PAT_WHEN = Pattern.compile(".*(" + PARSE_WHEN + ")" + GEN_FLD_PAT); + static final Pattern PAT_WHO = Pattern.compile(".*(" + PARSE_WHO + ")" + GEN_FLD_PAT); + static final Pattern PAT_WHY = Pattern.compile(".*(" + PARSE_WHY + ")" + GEN_FLD_PAT); + static final Pattern PAT_WHERE = Pattern.compile(".*(" + PARSE_WHERE + ")" + GEN_FLD_PAT); + static final Pattern PAT_CLTIP = Pattern.compile(".*(" + PARSE_CLIENT_IP + ")" + GEN_FLD_PAT); + static final Pattern PAT_WHAT_METH = Pattern.compile(".*(" + PARSE_WHAT_METH + ")" + GEN_FLD_PAT); + static final Pattern PAT_WHAT_API = Pattern.compile(".*(" + PARSE_WHAT_API + ")" + GEN_FLD_PAT); + static final Pattern PAT_WHAT_DOM = Pattern.compile(".*(" + PARSE_WHAT_DOM + ")" + GEN_FLD_PAT); + static final Pattern PAT_WHAT_ENT = Pattern.compile(".*(" + PARSE_WHAT_ENT + ")" + GEN_FLD_PAT); + + public DefaultAuditLogMsgBuilder() { + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#versionTag() + */ + public String versionTag() { + if (versionTag == null) { + StringBuilder sb = new StringBuilder(SB_MIN_SIZE_INIT); + sb.append(PARSE_VERS).append("=(").append(messageVersion).append(");"); + versionTag = sb.toString(); + } + return versionTag; + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#who(java.lang.String) + */ + @Override + public AuditLogMsgBuilder who(String whoVal) { + this.who = whoVal; + return this; + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#who() + */ + @Override + public String who() { + if (who == null) { + return NULL_STR; + } + return who; + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#why(java.lang.String) + */ + @Override + public AuditLogMsgBuilder why(String whyVal) { + this.why = whyVal; + return this; + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#why() + */ + @Override + public String why() { + if (why == null) { + return NULL_STR; + } + return why; + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#when(com.yahoo.data.Timestamp) + */ + @Override + public AuditLogMsgBuilder when(Timestamp ts) { + return when(ts.toString()); + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#when(java.lang.String) + */ + @Override + public AuditLogMsgBuilder when(String whenVal) { + this.when = whenVal; + return this; + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#when() + */ + @Override + public String when() { + if (when == null) { + return NULL_STR; + } + return when; + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#clientIp(java.lang.String) + */ + @Override + public AuditLogMsgBuilder clientIp(String clientIpAddr) { + this.clientIp = clientIpAddr; + return this; + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#clientIp() + */ + @Override + public String clientIp() { + if (clientIp == null) { + return NULL_STR; + } + return clientIp; + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whereIp(java.lang.String) + */ + @Override + public AuditLogMsgBuilder whereIp(String whereVal) { + this.whereIp = whereVal; + return this; + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whereHttpsPort(java.lang.String) + */ + @Override + public AuditLogMsgBuilder whereHttpsPort(String whereVal) { + this.whereHttpsPort = whereVal; + return this; + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whereHttpPort(java.lang.String) + */ + @Override + public AuditLogMsgBuilder whereHttpPort(String whereVal) { + this.whereHttpPort = whereVal; + return this; + } + + // Ex: '{"server-ip":"198.177.62.9","server-https-port":"4453","server-http-port":"10080"}' + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#where() + */ + @Override + public String where() { + if (whereIp == null) { + whereIp = NULL_STR; + } + StringBuilder sb = new StringBuilder(SB_MIN_SIZE_INIT); + sb.append("server-ip=").append(whereIp); + + if (whereHttpsPort == null) { + whereHttpsPort = NULL_STR; + } + sb.append(",server-https-port=").append(whereHttpsPort); + + if (whereHttpPort == null) { + whereHttpPort = NULL_STR; + } + sb.append(",server-http-port=").append(whereHttpPort); + + return sb.toString(); + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whatMethod(java.lang.String) + */ + @Override + public AuditLogMsgBuilder whatMethod(String whatMethodVal) { + this.whatMethod = whatMethodVal; + return this; + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whatMethod() + */ + @Override + public String whatMethod() { + if (whatMethod == null) { + return NULL_STR; + } + return whatMethod; + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whatApi(java.lang.String) + */ + @Override + public AuditLogMsgBuilder whatApi(String whatApiVal) { + this.whatApi = whatApiVal; + return this; + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whatApi() + */ + @Override + public String whatApi() { + if (whatApi == null) { + return NULL_STR; + } + return whatApi; + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whatDomain(java.lang.String) + */ + @Override + public AuditLogMsgBuilder whatDomain(String whatDomainVal) { + this.whatDomain = whatDomainVal; + return this; + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whatDomain() + */ + @Override + public String whatDomain() { + if (whatDomain == null) { + return NULL_STR; + } + return whatDomain; + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whatEntity(java.lang.String) + */ + @Override + public AuditLogMsgBuilder whatEntity(String whatEntityVal) { + this.whatEntity = whatEntityVal; + return this; + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whatEntity() + */ + @Override + public String whatEntity() { + if (whatEntity == null) { + return NULL_STR; + } + return whatEntity; + } + + /* Set/replace the whatDetails field. + * (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whatDetails(java.lang.String) + */ + @Override + public AuditLogMsgBuilder whatDetails(String whatDetailsVal) { + this.whatDetails = whatDetailsVal; + return this; + } + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whatDetails() + */ + @Override + public String whatDetails() { + if (whatDetails == null) { + return NULL_STR; + } + return whatDetails; + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#whatDetails(java.lang.String, com.yahoo.data.Struct, com.yahoo.data.Struct) + */ + @Override + public AuditLogMsgBuilder whatDetails(String tag, Struct origFields, Struct newFields) { + return whatDetails(whatSubDetails(tag, origFields, newFields)); + } + + // Determines all differences between the Struct of original fields and the new one. + // If the Struct contains a Struct field, this method will recur into that field. + // + String whatSubDetails(String tag, Struct origFields, Struct newFields) { + + Set setChanged = origFields.keySet(); + Set setOldDiff = new HashSet(setChanged); + Set setNew = newFields.keySet(); + Set setNewDiff = new HashSet(setNew); + setOldDiff.removeAll(setNewDiff); // gets a diff - contains Removed elements + + Set setOld = origFields.keySet(); + setNewDiff.removeAll(setOld); // gets a diff - contains Added elements + // HAVE: partial diff, all removed and added elements + + // for the intersection of set of keys, find any changes to the values + // + StringBuilder changedSb = new StringBuilder(SB_MED_SIZE_INIT); + changedSb.append(PARSE_DETAILS_CHANGED).append("=("); + setChanged.retainAll(setNew); // the intersection of the 2 sets + // if we have common keys, are the values different? + boolean changedValsFound = false; + for (Iterator it = setChanged.iterator(); it != null && it.hasNext();) { + String key = it.next(); + Object origVal = origFields.get(key); + if (origVal instanceof Timestamp) { + continue; // ignore timestamp fields + } + Object newVal = newFields.get(key); + if (Value.equals(origVal, newVal) == false) { + // the values have changed for this key + changedValsFound = true; + if (origVal instanceof Array) { + StringBuilder addedSetSb = new StringBuilder(SB_MED_SIZE_INIT); + addedSetSb.append(key + "=(").append(PARSE_DETAILS_ADDEDVALS).append("=("); + StringBuilder removedSetSb = new StringBuilder(SB_MED_SIZE_INIT); + removedSetSb.append(key + "=(").append(PARSE_DETAILS_REMOVEDVALS).append("=("); + + buildDiffArray((Array) origVal, (Array) newVal, addedSetSb, removedSetSb); + addedSetSb.append("));"); // end-of-= + removedSetSb.append("));"); // end-of-= + changedSb.append(addedSetSb.toString()); + changedSb.append(removedSetSb.toString()); + } else if (origVal instanceof Struct) { + String subDetails = whatSubDetails(key, (Struct) origVal, (Struct) newVal); + changedSb.append(subDetails); + } else { + changedSb.append(key + "=(").append(PARSE_FROM).append("=(").append(origVal); + changedSb.append(");").append(PARSE_TO).append("=(").append(newVal); + changedSb.append("));"); // end-of-= + } + } + } + // HAVE: full diff, elements with changed values, removed elements, added elements + + // if user didnt specify a tag, we will use a default to keep syntax + // consistent in the built string + String prefix = tag == null ? "TAG=(" : tag + "=("; + StringBuilder sb = new StringBuilder(SB_MED_SIZE_INIT); + sb.append(prefix); + + if (setChanged.size() > 0) { // HAVE: values were replaced + if (!changedValsFound) { + changedSb.append(NULL_STR); + } + } + changedSb.append(");"); // end-of-CHANGED= + sb.append(changedSb); + + sb.append(prefix).append(PARSE_DETAILS_REMOVED + "=("); + buildDiffKeys(setOldDiff, origFields, sb); + sb.append("));"); + + sb.append(prefix).append(PARSE_DETAILS_ADDED + "=("); + buildDiffKeys(setNewDiff, newFields, sb); + sb.append("));"); + + return sb.toString(); + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#build() + */ + @Override + public String build() { + StringBuilder sb = new StringBuilder(SB_MAX_SIZE_INIT); + sb.append(versionTag()).append(PARSE_WHEN).append("=(").append(when()). + append(");"). append(PARSE_WHO).append("=(").append(who()). + append(");").append(PARSE_WHY).append("=(").append(why()). + append(");").append(PARSE_WHERE).append("=(").append(where()). + append(");").append(PARSE_CLIENT_IP).append("=(").append(clientIp()); + sb.append(");").append(PARSE_WHAT_METH).append("=(").append(whatMethod()).append(");").append(PARSE_WHAT_API).append("=("); + sb.append(whatApi()).append(");").append(PARSE_WHAT_DOM).append("=(").append(whatDomain()).append(");").append(PARSE_WHAT_ENT).append("=("); + sb.append(whatEntity()).append(");").append(PARSE_WHAT_DETAILS).append("=(").append(whatDetails()).append(");"); + return sb.toString(); + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogMsgBuilder#parse(java.lang.String) + */ + @Override + public Struct parse(String logMsgBldrMsg) { + // reverse what build() does, and pull out each component of the string + CharSequence charSeq = logMsgBldrMsg.subSequence(0, logMsgBldrMsg.length()); + Struct msg = new Struct().with(PARSE_VERS, getMatchedGroup(PAT_VERS, 2, logMsgBldrMsg)); + msg.with(PARSE_WHEN, getMatchedGroup(PAT_WHEN, 2, charSeq)). + with(PARSE_WHO, getMatchedGroup(PAT_WHO, 2, charSeq)). + with(PARSE_WHY, getMatchedGroup(PAT_WHY, 2, charSeq)). + with(PARSE_WHERE, getMatchedGroup(PAT_WHERE, 2, charSeq)). + with(PARSE_CLIENT_IP, getMatchedGroup(PAT_CLTIP, 2, charSeq)). + with(PARSE_WHAT_METH, getMatchedGroup(PAT_WHAT_METH, 2, charSeq)). + with(PARSE_WHAT_API, getMatchedGroup(PAT_WHAT_API, 2, charSeq)). + with(PARSE_WHAT_DOM, getMatchedGroup(PAT_WHAT_DOM, 2, charSeq)). + with(PARSE_WHAT_ENT, getMatchedGroup(PAT_WHAT_ENT, 2, charSeq)); + + // now get the WHAT-details + int index = logMsgBldrMsg.indexOf(PARSE_WHAT_DETAILS + "=("); + if (index != -1) { + Struct details = new Struct(); + msg.with(PARSE_WHAT_DETAILS, details); + + String parseRemovedField = PARSE_DETAILS_REMOVED + "=("; + String parseAddedField = PARSE_DETAILS_ADDED + "=("; + + // first level entities of WHAT-details are: CHANGED, REMOVED, ADDED + // + int removedIndex = logMsgBldrMsg.lastIndexOf(parseRemovedField); + int addedIndex = logMsgBldrMsg.indexOf(parseAddedField, removedIndex); + if (removedIndex != -1) { + int removedValIndex = removedIndex + parseRemovedField.length(); + int endOfRemIndex = addedIndex; + if (addedIndex == -1) { + endOfRemIndex = logMsgBldrMsg.length(); + } + endOfRemIndex = logMsgBldrMsg.lastIndexOf(')', endOfRemIndex); + String removed = logMsgBldrMsg.substring(removedValIndex, addedIndex); + details.with(PARSE_DETAILS_REMOVED, removed); + } + + if (addedIndex != -1) { + String added = logMsgBldrMsg.substring(addedIndex + parseAddedField.length()); + details.with(PARSE_DETAILS_ADDED, added); + } + + // process the CHANGED section - which can contain embedded Struct's + // + int changedIndex = logMsgBldrMsg.indexOf(PARSE_DETAILS_CHANGED + "=", index); + if (changedIndex != -1) { + int endingIndex = removedIndex != -1 ? + removedIndex : + (addedIndex != -1 ? addedIndex : logMsgBldrMsg.length()); + String changed = logMsgBldrMsg.substring(changedIndex, endingIndex); + details.with(PARSE_DETAILS_CHANGED, changed); + + // CHANGED can contain multiple ADDED-VALUES, REMOVED-VALUES, {FROM,TO} pairs + // make lists for each of these and add to details + + Array addedValues = new Array(); + findChangedValues(addedValues, changed, PARSE_DETAILS_ADDEDVALS + "=(", "));", true); + if (addedValues.size() > 0) { + details.with(PARSE_DETAILS_ADDEDVALS, addedValues); + } + + Array removedValues = new Array(); + findChangedValues(removedValues, changed, PARSE_DETAILS_REMOVEDVALS + "=(", "));", true); + if (removedValues.size() > 0) { + details.with(PARSE_DETAILS_REMOVEDVALS, removedValues); + } + + // FROM/TO pairs + Array fromToValues = new Array(); + findChangedValues(fromToValues, changed, PARSE_FROM + "=(", "));", true); + if (fromToValues.size() > 0) { + details.with(PARSE_DETAILS_FROMTOVALS, fromToValues); + } + + // look for embedded Struct - ADDED/REMOVED fields + // if there are embedded Struct's in the original entity, then + // due to recursion there can be multiple REMOVED and ADDED entries + // puttenantroles is one of those ZMS api that can cause this + Array removedFields = new Array(); + findChangedValues(removedFields, changed, parseRemovedField, "));", true); + if (removedFields.size() > 0) { + details.with(PARSE_DETAILS_EMB_REMOVEDVALS, removedFields); + } + + Array addedFields = new Array(); + findChangedValues(addedFields, changed, parseAddedField, "));", true); + if (addedFields.size() > 0) { + details.with(PARSE_DETAILS_EMB_ADDEDVALS, addedFields); + } + } + } + return msg; + } + + void findChangedValues(Array values, String changed, String fieldName, String endFieldStr, boolean wantPrefix) { + int endOfArrayIndex = 0; + for (int valIndex = changed.indexOf(fieldName, endOfArrayIndex); + valIndex != -1; + valIndex = changed.indexOf(fieldName, endOfArrayIndex)) { + + endOfArrayIndex = changed.indexOf(endFieldStr, valIndex); + if (endOfArrayIndex == -1) { + break; + } + + String value = ""; + if (wantPrefix && (valIndex - 3 > 0)) { + // ex: ";org=(FROM=", "CHANGED=(modified=(FROM=(" + // go backwards from valIndex to either ';', or '(' + for (int cnt = valIndex - 2; cnt > -1; --cnt) { + char endChar = changed.charAt(cnt) ; + if (endChar == ';' || endChar == '(') { + valIndex = cnt + 1; + break; + } + } + } + + int addOffset = changed.length() > endOfArrayIndex ? 1 : 0; + value = changed.substring(valIndex, endOfArrayIndex + addOffset); + values.add(value); + } + } + + String getMatchedGroup(Pattern patty, int groupNum, CharSequence logMsg) { + Matcher pm = patty.matcher(logMsg); + if (pm.matches() && pm.groupCount() >= groupNum) { + return pm.group(groupNum); + } + return null; + } + + // Set the diff between the Arrays into the added set and/or the removed set StringBuilder. + // If the Array elements are Struct, it will set them appropriately but not recur into them. + // + void buildDiffArray(Array origVal, Array newVal, StringBuilder addedSetSb, StringBuilder removedSetSb) { + + // create set for each Array of elements + // + Set origValSet = new HashSet(); + Iterator elems = origVal.iterator(); + for (; elems != null && elems.hasNext(); ) { + Object obj = elems.next(); + StringBuilder sb = new StringBuilder(SB_MIN_SIZE_INIT); + Value.appendToString(obj, sb, null); + origValSet.add(sb.toString()); + } + + Set newValSet = new HashSet(); + for (elems = newVal.iterator(); elems != null && elems.hasNext(); ) { + Object obj = elems.next(); + StringBuilder sb = new StringBuilder(SB_MIN_SIZE_INIT); + Value.appendToString(obj, sb, null); + newValSet.add(sb.toString()); + } + // HAVE: set of serialized elements for original and new set of elements + + // Build the diff sets now + // + Set removedValSet = new HashSet(origValSet); + removedValSet.removeAll(newValSet); // gets a diff - contains Removed elements + newValSet.removeAll(origValSet); // gets a diff - contains Added elements + // HAVE: set of serialized elements for added and removed elements + + // print the set of removed elements + buildDiffValueSet(removedValSet, removedSetSb); + // print the set of added elements + buildDiffValueSet(newValSet, addedSetSb); + } + + // Set all the elements of the value Set into the StringBuilder + // + void buildDiffValueSet(Set valSet, StringBuilder sb) { + if (valSet.isEmpty()) { + sb.append(NULL_STR); + return; + } + for (Iterator elems = valSet.iterator(); elems != null && elems.hasNext();) { + sb.append(elems.next()); + if (elems.hasNext()) { + sb.append(","); + } + } + } + + // Set only the key/value pairs specified in the set of key names into the StringBuilder + // + void buildDiffKeys(Set setDiffKeyNames, Struct dataStruct, StringBuilder sb) { + + if (setDiffKeyNames.isEmpty()) { + sb.append(NULL_STR); + return; + } + Iterator it = setDiffKeyNames.iterator(); + while (it != null && it.hasNext()) { + String key = it.next(); + sb.append(key + "="); + Object val = dataStruct.get(key); + if (val == null) { + val = NULL_STR; + } + Value.appendToString(val, sb, null); + if (it.hasNext()) { + sb.append(","); + } + } + } + +} + diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/impl/DefaultAuditLogger.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/impl/DefaultAuditLogger.java new file mode 100644 index 00000000000..8d562512815 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/log/impl/DefaultAuditLogger.java @@ -0,0 +1,61 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.log.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.AuditLogger; + +/** + * This default implementation uses log4j API. + * The default constructor depends on a logger named "AuditSoxLogger" specified in the logback.xml + * The second constructor takes a name of the logger the caller wants to use. + */ +public class DefaultAuditLogger implements AuditLogger { + + // Configured logger named "AuditSoxLogger" + // + private static Logger AUDITLOGGER = LoggerFactory.getLogger("AuditSoxLogger"); + + public DefaultAuditLogger() { + } + + // Override the default logger with one named loggerName + // + public DefaultAuditLogger(String loggerName) { + AUDITLOGGER = LoggerFactory.getLogger(loggerName); + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogger#log(java.lang.String, java.lang.String) + */ + @Override + public void log(String logMsg, String msgVersionTag) { + AUDITLOGGER.info(logMsg); // ignore msgVersionTag for this logger implementation + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogger#log(com.yahoo.athenz.common.server.log.AuditLogMsgBuilder) + */ + @Override + public void log(AuditLogMsgBuilder msgBldr) { + if (msgBldr != null) { + log(msgBldr.build(), msgBldr.versionTag()); + } + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/Http.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/Http.java new file mode 100644 index 00000000000..1f128c900e1 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/Http.java @@ -0,0 +1,175 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.rest; + +import java.util.List; +import java.util.ArrayList; +import java.security.cert.X509Certificate; + +import javax.servlet.http.HttpServletRequest; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Authorizer; +import com.yahoo.athenz.auth.Principal; + +public class Http { + + public static final String INVALID_CRED_ATTR = "com.yahoo.athenz.auth.credential.error"; + public static final String LOOPBACK_ADDRESS = "127.0.0.1"; + public static final String XFF_HEADER = "X-Forwarded-For"; + public static final String JAVAX_CERT_ATTR = "javax.servlet.request.X509Certificate"; + + public static class AuthorityList { + List authorities; + + public AuthorityList() { + authorities = new ArrayList(); + } + + public void add(Authority a) { + authorities.add(a); + } + + public List getAuthorities() { + return authorities; + } + } + + static String getCookieValue(HttpServletRequest hreq, String name) { + + javax.servlet.http.Cookie[] cookies = hreq.getCookies(); + if (cookies == null) { + return null; + } + for (javax.servlet.http.Cookie cookie : cookies) { + if (name.equals(cookie.getName())) { + return cookie.getValue(); + } + } + return null; + } + + private static String authenticatingCredentials(HttpServletRequest request, + Authority authority) { + String header = authority.getHeader(); + String creds = header.startsWith("Cookie.") ? getCookieValue(request, + header.substring(7)) : request.getHeader(header); + return creds; + } + + public static String authenticatingCredentials(HttpServletRequest request, + AuthorityList authorities) { + if (authorities == null) { + return null; + } + for (Authority authority : authorities.authorities) { + String creds = authenticatingCredentials(request, authority); + if (creds != null) { + return creds; + } + } + return null; + } + + /** + * Return the remote client IP address. + * Detect if connection is from ATS by looking at XFF header. + * If XFF header, return the last address therein since it was added by ATS. + **/ + static String getRemoteAddress(final HttpServletRequest request) { + String addr = request.getRemoteAddr(); + if (addr.equals(LOOPBACK_ADDRESS)) { + String xff = request.getHeader(XFF_HEADER); + if (xff != null) { + String[] addrs = xff.split(","); + addr = addrs[addrs.length - 1].trim(); + } + } + return addr; + } + + public static Principal authenticate(HttpServletRequest request, + AuthorityList authorities) { + if (authorities == null) { + throw new ResourceException (ResourceException.INTERNAL_SERVER_ERROR, + "No authorities configured"); + } + + StringBuilder authErrMsg = new StringBuilder(512); + for (Authority authority : authorities.authorities) { + Principal principal = null; + StringBuilder errMsg = new StringBuilder(512); + switch (authority.getCredSource()) { + case HEADER: + String creds = authenticatingCredentials(request, authority); + if (creds != null) { + principal = authority.authenticate(creds, getRemoteAddress(request), request.getMethod(), errMsg); + } + break; + case CERTIFICATE: + X509Certificate[] certs = (X509Certificate[]) request.getAttribute(JAVAX_CERT_ATTR); + if (certs != null) { + principal = authority.authenticate(certs, errMsg); + } + break; + } + + if (principal != null) { + return principal; + } else { + authErrMsg.append(":error: ").append(errMsg); + } + } + // set the error message as a request attribute + request.setAttribute(INVALID_CRED_ATTR, authErrMsg.toString()); + throw new ResourceException (ResourceException.UNAUTHORIZED, "Invalid credentials"); + } + + public static String authenticatedUser(HttpServletRequest request, + AuthorityList authorities) { + Principal principal = authenticate(request, authorities); + return principal.getYRN(); + } + + public static String authorizedUser(HttpServletRequest request, + AuthorityList authorities, Authorizer authorizer, String action, + String resource, String otherDomain) { + Principal principal = authenticate(request, authorities); + authorize(authorizer, principal, action, resource, otherDomain); + if (principal == null) { + return null; + } + return principal.getYRN(); + } + + public static Principal authorize(Authorizer authorizer, Principal principal, + String action, String resource, String otherDomain) { + + if (action == null || resource == null) { + throw new ResourceException(ResourceException.BAD_REQUEST, + "Missing 'action' and/or 'resource' pararameters"); + } + if (authorizer != null) { + if (!authorizer.access(action, resource, principal, otherDomain)) { + throw new ResourceException(ResourceException.FORBIDDEN, "Forbidden"); + } + } else { + throw new ResourceException(ResourceException.INTERNAL_SERVER_ERROR, + "No authorizer configured in service"); + } + return principal; + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/HttpContainer.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/HttpContainer.java new file mode 100644 index 00000000000..116cfda56d7 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/HttpContainer.java @@ -0,0 +1,202 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.rest; + +import java.util.HashSet; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.glassfish.hk2.utilities.binding.AbstractBinder; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Authorizer; + +public class HttpContainer { + + protected Server server = null; + protected String banner = null; + private HandlerCollection handlers = null; + protected Http.AuthorityList authorities = null; + protected Authorizer authorizer = null; + + /** Currently not a public class. Use JettyContainer */ + protected HttpContainer() { + } + + protected HashSet> resources = new HashSet>(); + protected HashSet singletons = new HashSet(); + protected HashSet> singletonsCheck = new HashSet>(); + + /** + * registers the resource classes with the jersey server. + * @param classes the resource classes to register + * @return this container, so calls may be chained + */ + public HttpContainer resource(Class... classes) { + for (Class classObject : classes) { + resources.add(classObject); + } + return this; + } + + /** + * Inject the specified object as a singleton corresponding to the specified + * target type. Any registered resource can access this with the @Context + * injector. + * @param Describes the container type + * @param targetType the type of the target injection (often an interface class object) + * @param obj the object to use as a singleton for that type + * @return this container, so calls may be chained + */ + public HttpContainer injectSingleton(final Class targetType, final Object obj) { + + AbstractBinder binder = new AbstractBinder() { + Class type = targetType; + Object singleton = obj; + + @Override + protected void configure() { + bind(type).in(javax.inject.Singleton.class); + bind(type.cast(singleton)).to(type); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(256); + sb.append("Binder: contains type=").append(type).append(" and object=").append(singleton); + return sb.toString(); + } + }; + + try { + Class componentClass = binder.getClass(); + if (singletonsCheck.contains(componentClass)) { + throw new RuntimeException("Cannot create new registration for component type class " + + componentClass + ": Existing previous registration found for the type."); + } else { + singletonsCheck.add(componentClass); + } + singletons.add(binder); + return this; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /* + * Register javax.ws.rs.container.ContainerRequestFilter's with this method. + */ + public HttpContainer addContainerRequestFilter(final Class targetType) { + + AbstractBinder binder = new AbstractBinder() { + Class type = targetType; + + @Override + protected void configure() { + bind(type).to(javax.ws.rs.container.ContainerRequestFilter.class); + } + }; + + try { + singletons.add(binder); + return this; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public HttpContainer authority(Authority a) { + if (authorities == null) { + authorities = new Http.AuthorityList(); + } + authorities.add(a); + return this; + } + + public HttpContainer authorizer(Authorizer a) { + authorizer = a; + return this; + } + + public HttpContainer delegate(Class targetType, Object singleton) { + return injectSingleton(targetType, singleton); + } + + public HttpContainer delegate(AbstractBinder abstractBinder) { + return injectSingleton(abstractBinder); + } + + public HttpContainer injectSingleton(AbstractBinder abstractBinder) { + singletons.add(abstractBinder); + return this; + } + + /** + * Set the banner that get displayed when server is started up. + * @param banner Banner text to be displayed + */ + public void setBanner(String banner) { + this.banner = banner; + } + + public void createServer(int maxThreads) { + + // Setup Thread pool + + QueuedThreadPool threadPool = new QueuedThreadPool(); + threadPool.setMaxThreads(maxThreads); + + server = new Server(threadPool); + setHandlers(new HandlerCollection()); + server.setHandler(getHandlers()); + } + + public Server getServer() { + return server; + } + + public void run(String base) { + try { + server.start(); + System.out.println("Jetty server running at " + banner); + server.join(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e.getMessage()); + } + } + + public void stop() { + try { + server.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public HandlerCollection getHandlers() { + return handlers; + } + + public void setHandlers(HandlerCollection handlers) { + this.handlers = handlers; + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/ResourceContext.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/ResourceContext.java new file mode 100644 index 00000000000..25620ce0eb8 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/ResourceContext.java @@ -0,0 +1,69 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.rest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.yahoo.athenz.auth.Authorizer; +import com.yahoo.athenz.auth.Principal; + +public class ResourceContext { + private final HttpServletRequest request; + private final HttpServletResponse response; + private final Http.AuthorityList authorities; + private final Authorizer authorizer; + + protected Principal principal; + protected boolean checked; + + public ResourceContext(HttpServletRequest request, HttpServletResponse response, + Http.AuthorityList authorities, Authorizer authorizer) { + this.request = request; + this.response = response; + this.authorities = authorities; + this.authorizer = authorizer; + this.principal = null; + this.checked = false; + } + + public HttpServletRequest request() { + return request; + } + + public HttpServletResponse response() { + return response; + } + + public Principal principal() { + return principal; + } + + //throws an exception if it cannot authenticate + public Principal authenticate() { + if (!checked) { + checked = true; + principal = Http.authenticate(request, authorities); + } + return principal; + } + + //throws an exception if it cannot authorize + public void authorize(String action, String resource, String trustedDomain) { + principal = authenticate(); + Http.authorize(authorizer, principal, action, resource, trustedDomain); + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/ResourceException.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/ResourceException.java new file mode 100644 index 00000000000..5f636334de9 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/ResourceException.java @@ -0,0 +1,164 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.rest; + +public class ResourceException extends RuntimeException { + + private static final long serialVersionUID = 2289910486634456175L; + + public final static int OK = 200; + public final static int CREATED = 201; + public final static int ACCEPTED = 202; + public final static int NO_CONTENT = 204; + public final static int MOVED_PERMANENTLY = 301; + public final static int FOUND = 302; + public final static int SEE_OTHER = 303; + public final static int NOT_MODIFIED = 304; + public final static int TEMPORARY_REDIRECT = 307; + public final static int BAD_REQUEST = 400; + public final static int UNAUTHORIZED = 401; + public final static int FORBIDDEN = 403; + public final static int NOT_FOUND = 404; + public final static int CONFLICT = 409; + public final static int GONE = 410; + public final static int PRECONDITION_FAILED = 412; + public final static int UNSUPPORTED_MEDIA_TYPE = 415; + public final static int INTERNAL_SERVER_ERROR = 500; + public final static int NOT_IMPLEMENTED = 501; + public final static int SERVICE_UNAVAILABLE = 503; + + public static int codeForSymbol(String sym) { + String symbol = sym.toUpperCase(); + if (symbol.contains("OK")) { + return OK; + } else if ("CREATED".equals(symbol)) { + return CREATED; + } else if ("ACCEPTED".equals(symbol)) { + return ACCEPTED; + } else if ("NO_CONTENT".equals(symbol)) { + return NO_CONTENT; + } else if ("MOVED_PERMANENTLY".equals(symbol)) { + return MOVED_PERMANENTLY; + } else if ("FOUND".equals(symbol)) { + return FOUND; + } else if ("SEE_OTHER".equals(symbol)) { + return SEE_OTHER; + } else if ("NOT_MODIFIED".equals(symbol)) { + return NOT_MODIFIED; + } else if ("TEMPORARY_REDIRECT".equals(symbol)) { + return TEMPORARY_REDIRECT; + } else if ("BAD_REQUEST".equals(symbol)) { + return BAD_REQUEST; + } else if ("UNAUTHORIZED".equals(symbol)) { + return UNAUTHORIZED; + } else if ("FORBIDDEN".equals(symbol)) { + return FORBIDDEN; + } else if ("NOT_FOUND".equals(symbol)) { + return NOT_FOUND; + } else if ("CONFLICT".equals(symbol)) { + return CONFLICT; + } else if ("GONE".equals(symbol)) { + return GONE; + } else if ("PRECONDITION_FAILED".equals(symbol)) { + return PRECONDITION_FAILED; + } else if ("INTERNAL_SERVER_ERROR".equals(symbol)) { + return INTERNAL_SERVER_ERROR; + } else if ("NOT_IMPLEMENTED".equals(symbol)) { + return NOT_IMPLEMENTED; + } else if ("UNSUPPORTED_MEDIA_TYPE".equals(symbol)) { + return UNSUPPORTED_MEDIA_TYPE; + } else if ("SERVICE_UNAVAILABLE".equals(symbol)) { + return SERVICE_UNAVAILABLE; + } else { + try { + return Integer.parseInt(sym); + } catch (NumberFormatException e) { + } + } + return 0; + } + + public static String symbolForCode(int code) { + switch (code) { + case OK: + return "OK"; + case CREATED: + return "CREATED"; + case ACCEPTED: + return "ACCEPTED"; + case NO_CONTENT: + return "NO_CONTENT"; + case MOVED_PERMANENTLY: + return "MOVED_PERMANENTLY"; + case FOUND: + return "FOUND"; + case SEE_OTHER: + return "SEE_OTHER"; + case NOT_MODIFIED: + return "NOT_MODIFIED"; + case TEMPORARY_REDIRECT: + return "TEMPORARY_REDIRECT"; + case BAD_REQUEST: + return "BAD_REQUEST"; + case UNAUTHORIZED: + return "UNAUTHORIZED"; + case FORBIDDEN: + return "FORBIDDEN"; + case NOT_FOUND: + return "NOT_FOUND"; + case CONFLICT: + return "CONFLICT"; + case GONE: + return "GONE"; + case PRECONDITION_FAILED: + return "PRECONDITION_FAILED"; + case INTERNAL_SERVER_ERROR: + return "INTERNAL_SERVER_ERROR"; + case NOT_IMPLEMENTED: + return "NOT_IMPLEMENTED"; + case UNSUPPORTED_MEDIA_TYPE: + return "UNSUPPORTED_MEDIA_TYPE"; + case SERVICE_UNAVAILABLE: + return "SERVICE_UNAVAILABLE"; + } + return null; + } + + int code; + Object data; + + public ResourceException(int code) { + this(code, symbolForCode(code)); + } + + public ResourceException(int code, Object data) { + super("ResourceException (" + code + "): " + data); + this.code = code; + this.data = data; + } + + public int getCode() { + return code; + } + + public Object getData() { + return data; + } + + public T getData(Class cl) { + return cl.cast(data); + } +} diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/RestCoreResourceConfig.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/RestCoreResourceConfig.java new file mode 100644 index 00000000000..7d101da9077 --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/rest/RestCoreResourceConfig.java @@ -0,0 +1,123 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.rest; + +import java.util.HashSet; +import java.util.HashMap; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.jackson.JacksonFeature; +import org.glassfish.hk2.utilities.binding.AbstractBinder; + +/* + * This is used to setup the application configuration for authorities, + * authorizer, supported content-providers, and delegate. + */ +public class RestCoreResourceConfig extends ResourceConfig { + + // map of authorizer and authority objects + @SuppressWarnings("rawtypes") + HashMap authObjMap = new HashMap(); + + // typically contains delegate binding (AbstractBinder) + // + HashSet singletons; + + + public RestCoreResourceConfig(HashSet> resources, HashSet singletonSet) { + if (resources == null || resources.isEmpty()) { + throw new ResourceException(ResourceException.BAD_REQUEST, "Missing required parameter: resources"); + } + + StringBuilder packageList = new StringBuilder(256); + int pkgCnt = 0; + for (Class klass: resources) { + Package pkg = klass.getPackage(); + if (pkg == null) { + continue; + } + + String pkgName = pkg.getName(); + if (pkgName == null || pkgName.length() == 0) { + continue; + } + + if (pkgCnt > 0) { + packageList.append(";"); + } + packageList.append(pkgName); + ++pkgCnt; + } + + setupPackages(packageList.toString()); + + setSingletons(singletonSet); + } + + void setupPackages(String packageList) { + packages(packageList) + .register(JacksonFeature.class); + } + + public void setSingletons(HashSet singletonSet) { + if (singletonSet == null) { + return; + } else if (singletons == null) { + singletons = singletonSet; + } else { + for (Object singletonObj : singletonSet) { + singletons.add(singletonObj); + } + } + } + + // for setting Authority list and Authorizer + @SuppressWarnings("rawtypes") + public void setAuthorityObject(Class klassType, Object authObj) { + authObjMap.put(klassType, authObj); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void registerAll() { + // register the authority objects + if (authObjMap.isEmpty()) { + throw new ResourceException(ResourceException.BAD_REQUEST, "Missing required parameter: authorizer or authorities"); + } + + AbstractBinder binder = new AbstractBinder() { + final HashMap authMap = authObjMap; + + @Override + protected void configure() { + for (Class klass: authMap.keySet()) { + bind(klass).in(javax.inject.Singleton.class); + bind(klass.cast(authMap.get(klass))).to(klass); + } + } + }; + registerInstances(binder); + + if (singletons == null || singletons.isEmpty()) { + throw new ResourceException(ResourceException.BAD_REQUEST, "Missing required parameter: singletons (delegate for the resource)"); + } + + // register the singletons + for (Object singletonObj : singletons) { + registerInstances(singletonObj); + } + } +} + diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/util/ServletRequestUtil.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/util/ServletRequestUtil.java new file mode 100644 index 00000000000..544ef5b545e --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/util/ServletRequestUtil.java @@ -0,0 +1,55 @@ + +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.util; + +import javax.servlet.http.HttpServletRequest; +import org.eclipse.jetty.server.Request; + +import com.yahoo.rdl.Timestamp; + +public class ServletRequestUtil { + + public static final String LOOPBACK_ADDRESS = "127.0.0.1"; + public static final String XFF_HEADER = "X-Forwarded-For"; + + /** + * Return the remote client IP address. + * Detect if connection is from ATS by looking at XFF header. + * If XFF header, return the last address therein since it was added by ATS. + **/ + public static String getRemoteAddress(final HttpServletRequest request) { + String addr = request.getRemoteAddr(); + if (LOOPBACK_ADDRESS.equals(addr)) { + String xff = request.getHeader(XFF_HEADER); + if (xff != null) { + String[] addrs = xff.split(","); + addr = addrs[addrs.length - 1].trim(); + } + } + return addr; + } + + /** + * Returns RFC3339 based time stamp String. + **/ + public static String getTimeStamp(final Request request) { + Timestamp ts = Timestamp.fromMillis(request.getTimeStamp()); + return ts.toString(); + } + +} + diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/util/StringUtils.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/util/StringUtils.java new file mode 100644 index 00000000000..2fc2662284d --- /dev/null +++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/server/util/StringUtils.java @@ -0,0 +1,67 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.util; + +public class StringUtils { + + public static String removeLeadingAndTrailingQuotes(String value) { + if (value.startsWith("\"")) { + value = value.substring(1, value.length()); + } + if (value.endsWith("\"")) { + value = value.substring(0, value.length() - 1); + } + return value; + } + + public static boolean isRegexMetaCharacter(char regexChar) { + switch (regexChar) { + case '^': + case '$': + case '.': + case '|': + case '[': + case '+': + case '\\': + case '(': + case ')': + case '{': + return true; + default: + return false; + } + } + + public static String patternFromGlob(String glob) { + StringBuilder sb = new StringBuilder("^"); + int len = glob.length(); + for (int i = 0; i < len; i++) { + char c = glob.charAt(i); + if (c == '*') { + sb.append(".*"); + } else if (c == '?') { + sb.append('.'); + } else { + if (isRegexMetaCharacter(c)) { + sb.append('\\'); + } + sb.append(c); + } + } + sb.append("$"); + return sb.toString(); + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugKerberosAuthorityTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugKerberosAuthorityTest.java new file mode 100644 index 00000000000..70038ce530e --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugKerberosAuthorityTest.java @@ -0,0 +1,95 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.debug; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.common.server.debug.DebugKerberosAuthority; + +import static org.testng.Assert.*; + +public class DebugKerberosAuthorityTest { + + static final String KRB_TOKEN = "YIIB6wYJKoZIhvcSAQICAQBuggHaMIIB1qADAgEFoQMCAQ6iBwMFAAAAAACjggEGYYIBAjCB/6ADAgEFoQ0bC0VYQU1QTEUuQ09NojAwLqADAgEAoScwJRsHZGF0YWh1YhsTZGF0YWh1Yi5leGFtcGxlLmNvbRsFYnVsbHmjgbYwgbOgAwIBA6KBqwSBqMJGf4H5nrRIdDyNxHp5fwxW6lsiFi+qUjryPvgiOAl/XldfwmKd9wXbQn00VBNhK+oVxmKv0V0J80e4oTdUnc+NlU/BJNCfsLPFTdYntc4A/ffdnsY7/U5HktTaWMfhvWxYocvhqISFTIFUT1+pH5742IWYNTgvFd5vkudibB3ijCanbMYv9CQXEjV+380rnf3gdLD2JGuxmaU78aJjDDKETL6Ck/qz8KSBtjCBs6ADAgEDooGrBIGoMrzLCTUi59wEoWX02+42K5m1MzW6HMNSuvfQeVGJdzPBsiFmZweNfJF6L9LdmLjQR4jSVUhVo3neFZmUN8G532wvZeKbHOtkXTnLRRdif+DoKyI8GOkbHu1CZlevcQZ0sgzyiH0wfQ/0nguE4kH7a2bM7HlV7N6MRGkC4DDkJZDNHxQr27FbZqrqEyw498HXPTtF93JGsKjXB8Z/wDaPs4PpdfoThTol"; + private static final String ATHENZ_USER_DOMAIN = "athenz.user_domain"; + private static final String USER_DOMAIN = System.getProperty(ATHENZ_USER_DOMAIN, "user"); + + @Test + public void testDebugKerberosAuthority() { + + Authority authority = new DebugKerberosAuthority(); + assertNotNull(authority); + + authority.initialize(); + + assertEquals(authority.getDomain(), USER_DOMAIN); + assertEquals(authority.getHeader(), DebugKerberosAuthority.KRB_HEADER); + + // invalid authenticate values + assertNull(authority.authenticate(null, "6.21.20.16", "GET", null)); + assertNull(authority.authenticate("abc", "6.21.20.16", "GET", null)); + assertNull(authority.authenticate(KRB_TOKEN, "6.21.20.16", "GET", null)); + + // valid values + Principal prnc = authority.authenticate(DebugKerberosAuthority.TOKEN_PREFIX + " " + KRB_TOKEN, "6.21.20.16", "GET", null); + assertNotNull(prnc); + assertEquals(prnc.getDomain(), USER_DOMAIN); + assertEquals(prnc.getName(), "anonymous"); + assertEquals(prnc.getCredentials(), KRB_TOKEN); + assertNull(prnc.getRoles()); + } + + @Test + public void testDebugKerberosAuthoritySysProp() { + + System.setProperty(DebugKerberosAuthority.ATHENZ_PROP_USER_NAME, "tiesto"); + + Authority authority = new DebugKerberosAuthority(); + assertNotNull(authority); + + authority.initialize(); + + assertEquals(authority.getDomain(), USER_DOMAIN); + assertEquals(authority.getHeader(), DebugKerberosAuthority.KRB_HEADER); + + // invalid authenticate values + assertNull(authority.authenticate(null, "6.21.20.16", "GET", null)); + assertNull(authority.authenticate("abc", "6.21.20.16", "GET", null)); + assertNull(authority.authenticate(KRB_TOKEN, "6.21.20.16", "GET", null)); + + // valid values + Principal prnc = authority.authenticate(DebugKerberosAuthority.TOKEN_PREFIX + " " + KRB_TOKEN, "6.21.20.16", "GET", null); + assertNotNull(prnc); + assertEquals(prnc.getDomain(), USER_DOMAIN); + assertEquals(prnc.getName(), "tiesto"); + assertEquals(prnc.getCredentials(), KRB_TOKEN); + assertNull(prnc.getRoles()); + + // now use debug token that contains user name + String token = DebugKerberosAuthority.TOKEN_PREFIX + " " + DebugKerberosAuthority.TOKEN_DEBUG_USER_FIELD + "jamesdean"; + prnc = authority.authenticate(token, "6.21.20.16", "GET", null); + assertNotNull(prnc); + assertEquals(prnc.getDomain(), USER_DOMAIN); + assertEquals(prnc.getName(), "jamesdean"); + assertEquals(prnc.getCredentials(), DebugKerberosAuthority.TOKEN_DEBUG_USER_FIELD + "jamesdean"); + assertNull(prnc.getRoles()); + + System.clearProperty(DebugKerberosAuthority.ATHENZ_PROP_USER_NAME); + } +} + diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugPrincipalAuthorityTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugPrincipalAuthorityTest.java new file mode 100644 index 00000000000..35536eb68cd --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugPrincipalAuthorityTest.java @@ -0,0 +1,57 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.debug; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority; + +import static org.testng.Assert.*; + +public class DebugPrincipalAuthorityTest { + + @Test + public void testPrincipalAuthority() { + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + assertNotNull(principalAuthority); + + principalAuthority.initialize(); + ((DebugPrincipalAuthority) principalAuthority).setKeyStore(null); + + assertNull(principalAuthority.getDomain()); + assertEquals(principalAuthority.getHeader(), "Athenz-Principal-Auth"); + + // invalid authenticate values + + assertNull(principalAuthority.authenticate(null, "10.11.12.13", "GET", null)); + assertNull(principalAuthority.authenticate("abc", "10.11.12.13", "GET", null)); + assertNull(principalAuthority.authenticate("v=S1;d=coretech;s=signature", "10.11.12.13", "GET", null)); + assertNull(principalAuthority.authenticate("v=S1;n=storage;s=signature", "10.11.12.13", "GET", null)); + + // valid values + + String token = "v=S1;d=coretech;n=storage;s=signature"; + Principal p = principalAuthority.authenticate(token, "10.11.12.13", "GET", null); + assertNotNull(p); + assertEquals(p.getDomain(), "coretech"); + assertEquals(p.getName(), "storage"); + assertEquals(p.getCredentials(), token); + assertNull(p.getRoles()); + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugRoleAuthorityTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugRoleAuthorityTest.java new file mode 100644 index 00000000000..3b1d7f07593 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugRoleAuthorityTest.java @@ -0,0 +1,65 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.debug; + +import java.util.List; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.common.server.debug.DebugRoleAuthority; + +import static org.testng.Assert.*; + +public class DebugRoleAuthorityTest { + + @Test + public void testRoleAuthority() { + + Authority roleAuthority = new com.yahoo.athenz.common.server.debug.DebugRoleAuthority(); + assertNotNull(roleAuthority); + + roleAuthority.initialize(); + ((DebugRoleAuthority) roleAuthority).setKeyStore(null); + + assertNull(roleAuthority.getDomain()); + assertEquals(roleAuthority.getHeader(), "Athenz-Role-Auth"); + + // invalid authenticate values + assertNull(roleAuthority.authenticate(null, "10.11.12.13", "GET", null)); + assertNull(roleAuthority.authenticate("abc", "10.11.12.13", "GET", null)); + assertNull(roleAuthority.authenticate("v=Z1;d=coretech;s=signature", "10.11.12.13", "GET", null)); + assertNull(roleAuthority.authenticate("v=Z1;r=role1,role2,role3;s=signature", "10.11.12.13", "GET", null)); + assertNull(roleAuthority.authenticate("v=U1;d=coretech;r=role1,role2,role3;s=signature", "10.11.12.13", "GET", null)); + + // valid values + + String token = "v=Z1;d=coretech;r=role1,role2,role3;s=signature"; + Principal p = roleAuthority.authenticate(token, "10.11.12.13", "GET", null); + assertNotNull(p); + assertEquals(p.getDomain(), "coretech"); + assertEquals(p.getCredentials(), token); + + assertNull(p.getName()); + + List roles = p.getRoles(); + assertEquals(roles.size(), 3); + assertTrue(roles.contains("role1")); + assertTrue(roles.contains("role2")); + assertTrue(roles.contains("role3")); + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugUserAuthorityTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugUserAuthorityTest.java new file mode 100644 index 00000000000..7e8bf55d555 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/debug/DebugUserAuthorityTest.java @@ -0,0 +1,52 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.debug; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; + +import static org.testng.Assert.*; + +public class DebugUserAuthorityTest { + + @Test + public void testUserAuthority() { + + Authority userAuthority = new com.yahoo.athenz.common.server.debug.DebugUserAuthority(); + assertNotNull(userAuthority); + + userAuthority.initialize(); + + assertEquals(userAuthority.getDomain(), "user"); + assertEquals(userAuthority.getHeader(), "Authorization"); + assertFalse(userAuthority.allowAuthorization()); + + // invalid authenticate values + assertNull(userAuthority.authenticate("Test Creds", "10.11.12.13", "GET", null)); + assertNull(userAuthority.authenticate("Basic !@#$#!@$#", "10.11.12.13", "GET", null)); + assertNull(userAuthority.authenticate("BasicdGVzdHVzZXI6dGVzdHB3ZA==", "10.11.12.13", "GET", null)); + + // valid values + + String token = "Basic dGVzdHVzZXI6dGVzdHB3ZA=="; + Principal p = userAuthority.authenticate(token, "10.11.12.13", "GET", null); + assertNotNull(p); + assertEquals(p.getDomain(), "user"); + assertEquals(p.getName(), "testuser"); + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/ETagFilterTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/ETagFilterTest.java new file mode 100644 index 00000000000..1a085a0a861 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/ETagFilterTest.java @@ -0,0 +1,64 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.filters; + +import static org.testng.Assert.*; + +import javax.ws.rs.core.EntityTag; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.container.ContainerResponseContext; + +import org.mockito.Mockito; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.common.server.filters.ETagFilter; + +public class ETagFilterTest { + + ContainerResponseContext getContext(String tag) { + ContainerResponseContext context = Mockito.mock(ContainerResponseContext.class); + MultivaluedMap mvMap = new MultivaluedHashMap(); + mvMap.add(HttpHeaders.ETAG, tag); + Mockito.when(context.getHeaders()).thenReturn(mvMap); + return context; + } + + @Test + public void testFilterContainerETagSet() { + + ContainerResponseContext containerResponse = getContext("etag"); + + ETagFilter eTagFilter = new ETagFilter(); + eTagFilter.filter(null, containerResponse); + EntityTag eTag = (EntityTag) containerResponse.getHeaders().getFirst(HttpHeaders.ETAG); + assertNotNull(eTag); + assertEquals(eTag.getValue(), "etag"); + } + + @Test + public void testFilterContainerETagNotSet() { + + ContainerResponseContext containerResponse = getContext(null); + + ETagFilter eTagFilter = new ETagFilter(); + eTagFilter.filter(null, containerResponse); + EntityTag eTag = (EntityTag) containerResponse.getHeaders().getFirst(HttpHeaders.ETAG); + assertNull(eTag); + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/MockHttpServletRequest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/MockHttpServletRequest.java new file mode 100644 index 00000000000..ecd95c6e5ee --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/MockHttpServletRequest.java @@ -0,0 +1,398 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.filters; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; + +public class MockHttpServletRequest implements HttpServletRequest { + + private String method; + private String uri; + + @Override + public AsyncContext getAsyncContext() { + return null; + } + + @Override + public Object getAttribute(String arg0) { + return null; + } + + @Override + public Enumeration getAttributeNames() { + return null; + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public int getContentLength() { + return 0; + } + + @Override + public long getContentLengthLong() { + return 0; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public DispatcherType getDispatcherType() { + return null; + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return null; + } + + @Override + public String getLocalAddr() { + return null; + } + + @Override + public String getLocalName() { + return null; + } + + @Override + public int getLocalPort() { + return 0; + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public Enumeration getLocales() { + return null; + } + + @Override + public String getParameter(String arg0) { + return null; + } + + @Override + public Map getParameterMap() { + return null; + } + + @Override + public Enumeration getParameterNames() { + return null; + } + + @Override + public String[] getParameterValues(String arg0) { + return null; + } + + @Override + public String getProtocol() { + return null; + } + + @Override + public BufferedReader getReader() throws IOException { + return null; + } + + @Override + public String getRealPath(String arg0) { + return null; + } + + @Override + public String getRemoteAddr() { + return null; + } + + @Override + public String getRemoteHost() { + return null; + } + + @Override + public int getRemotePort() { + return 0; + } + + @Override + public RequestDispatcher getRequestDispatcher(String arg0) { + return null; + } + + @Override + public String getScheme() { + return null; + } + + @Override + public String getServerName() { + return null; + } + + @Override + public int getServerPort() { + return 0; + } + + @Override + public ServletContext getServletContext() { + return null; + } + + @Override + public boolean isAsyncStarted() { + return false; + } + + @Override + public boolean isAsyncSupported() { + return false; + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public void removeAttribute(String arg0) { + } + + @Override + public void setAttribute(String arg0, Object arg1) { + } + + @Override + public void setCharacterEncoding(String arg0) + throws UnsupportedEncodingException { + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1) + throws IllegalStateException { + return null; + } + + @Override + public boolean authenticate(HttpServletResponse arg0) + throws IOException, ServletException { + return false; + } + + @Override + public String changeSessionId() { + return null; + } + + @Override + public String getAuthType() { + return null; + } + + @Override + public String getContextPath() { + return null; + } + + @Override + public Cookie[] getCookies() { + return null; + } + + @Override + public long getDateHeader(String arg0) { + return 0; + } + + @Override + public String getHeader(String arg0) { + return null; + } + + @Override + public Enumeration getHeaderNames() { + return null; + } + + @Override + public Enumeration getHeaders(String arg0) { + return null; + } + + @Override + public int getIntHeader(String arg0) { + return 0; + } + + @Override + public String getMethod() { + return method; + } + + @Override + public Part getPart(String arg0) throws IOException, ServletException { + return null; + } + + @Override + public Collection getParts() throws IOException, ServletException { + return null; + } + + @Override + public String getPathInfo() { + return null; + } + + @Override + public String getPathTranslated() { + return null; + } + + @Override + public String getQueryString() { + return null; + } + + @Override + public String getRemoteUser() { + return null; + } + + @Override + public String getRequestURI() { + return uri; + } + + @Override + public StringBuffer getRequestURL() { + return null; + } + + @Override + public String getRequestedSessionId() { + return null; + } + + @Override + public String getServletPath() { + return null; + } + + @Override + public HttpSession getSession() { + return null; + } + + @Override + public HttpSession getSession(boolean arg0) { + return null; + } + + @Override + public Principal getUserPrincipal() { + return null; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + return false; + } + + @Override + public boolean isRequestedSessionIdValid() { + return false; + } + + @Override + public boolean isUserInRole(String arg0) { + return false; + } + + @Override + public void login(String arg0, String arg1) throws ServletException { + } + + @Override + public void logout() throws ServletException { + } + + @Override + public T upgrade(Class arg0) + throws IOException, ServletException { + return null; + } + + public void setMethod(String method) { + this.method = method; + } + + public void setRequestURI(String uri) { + this.uri = uri; + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/MockHttpServletResponse.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/MockHttpServletResponse.java new file mode 100644 index 00000000000..403d78cccb0 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/filters/MockHttpServletResponse.java @@ -0,0 +1,215 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.filters; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Locale; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + + +public class MockHttpServletResponse implements HttpServletResponse { + + private int contentLength = 0; + private int status = 0; + private ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); + private PrintWriter writer = new PrintWriter(byteOutputStream); + + public String getWriterData() throws IOException { + writer.flush(); + byteOutputStream.flush(); + return new String(byteOutputStream.toByteArray(), StandardCharsets.UTF_8); + } + + @Override + public void flushBuffer() throws IOException { + } + + @Override + public int getBufferSize() { + return 0; + } + + public int getContentLength() { + return contentLength; + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + return null; + } + + @Override + public PrintWriter getWriter() throws IOException { + return writer; + } + + @Override + public boolean isCommitted() { + return false; + } + + @Override + public void reset() { + + } + + @Override + public void resetBuffer() { + + } + + @Override + public void setBufferSize(int arg0) { + } + + @Override + public void setCharacterEncoding(String arg0) { + } + + @Override + public void setContentLength(int length) { + contentLength = length; + } + + @Override + public void setContentLengthLong(long arg0) { + } + + @Override + public void setContentType(String arg0) { + } + + @Override + public void setLocale(Locale arg0) { + } + + @Override + public void addCookie(Cookie arg0) { + } + + @Override + public void addDateHeader(String arg0, long arg1) { + } + + @Override + public void addHeader(String arg0, String arg1) { + } + + @Override + public void addIntHeader(String arg0, int arg1) { + } + + @Override + public boolean containsHeader(String arg0) { + return false; + } + + @Override + public String encodeRedirectURL(String arg0) { + return null; + } + + @Override + public String encodeRedirectUrl(String arg0) { + return null; + } + + @Override + public String encodeURL(String arg0) { + return null; + } + + @Override + public String encodeUrl(String arg0) { + return null; + } + + @Override + public String getHeader(String arg0) { + return null; + } + + @Override + public Collection getHeaderNames() { + return null; + } + + @Override + public Collection getHeaders(String arg0) { + return null; + } + + @Override + public int getStatus() { + return status; + } + + @Override + public void sendError(int arg0) throws IOException { + } + + @Override + public void sendError(int arg0, String arg1) throws IOException { + } + + @Override + public void sendRedirect(String arg0) throws IOException { + } + + @Override + public void setDateHeader(String arg0, long arg1) { + } + + @Override + public void setHeader(String arg0, String arg1) { + } + + @Override + public void setIntHeader(String arg0, int arg1) { + } + + @Override + public void setStatus(int arg0) { + status = arg0; + } + + @Override + public void setStatus(int arg0, String arg1) { + status = arg0; + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/AthenzRequestLogTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/AthenzRequestLogTest.java new file mode 100644 index 00000000000..5434c93361d --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/AthenzRequestLogTest.java @@ -0,0 +1,216 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.log; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; + +import org.mockito.Mockito; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.testng.annotations.Test; +import org.testng.annotations.BeforeClass; + +import static org.testng.Assert.*; + +import com.yahoo.athenz.common.server.log.AthenzRequestLog; +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.rdl.Timestamp; + +public class AthenzRequestLogTest { + + @Mock Request mockRequest; + + long currentTimeMillis = System.currentTimeMillis(); + java.util.Enumeration testHeadersEnum = null; + + final static String CRED_IN_ERROR = "v=S1;d=cd.project;n=nfl;h=somehost.somecompany.com;a=saltvalue;t=1447361732;e=1447361742"; + + final static String CRED_ATTR_ERROR = ":error: PrincipalAuthority:authenticate: service token validation failure: Token:validate: token=v=S1;d=cd.project;n=nfl;h=somehost.somecompany.com;a=saltvalue;t=1447361732;e=1447361742 : has expired time=1447361742 : current time=1447465088 : credential=" + CRED_IN_ERROR; + + final static java.util.Set auditLogMsgs = new java.util.HashSet(); + final static java.util.Set jettyLogMsgs = new java.util.HashSet(); + + static AuditLogger auditLogger; + + static org.eclipse.jetty.util.log.Logger jettyLogger; + + @BeforeClass + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + Mockito.when(mockRequest.getRemoteAddr()).thenReturn("11.16.20.15"); + Mockito.when(mockRequest.getLocalName()).thenReturn("FakeTestHostName"); + Mockito.when(mockRequest.getServerPort()).thenReturn(4666); + Mockito.when(mockRequest.getRequestURI()).thenReturn("/domain/jupiter/moons"); + Mockito.when(mockRequest.getRemoteUser()).thenReturn("FakeUserId"); + Mockito.when(mockRequest.getMethod()).thenReturn("GET"); + Mockito.when(mockRequest.getAttribute("com.yahoo.athenz.auth.credential.error")).thenReturn(CRED_ATTR_ERROR); + Mockito.when(mockRequest.getTimeStamp()).thenReturn(currentTimeMillis); + + java.util.Hashtable reqHeaders = new java.util.Hashtable<>(); + testHeadersEnum = reqHeaders.keys(); + Mockito.when(mockRequest.getHeaderNames()).thenReturn(testHeadersEnum); + org.eclipse.jetty.http.HttpURI httpUri = new org.eclipse.jetty.http.HttpURI(); + Mockito.when(mockRequest.getHttpURI()).thenReturn(httpUri); + + auditLogger = new AuditLogger() { + @Override + public void log(String msg, String msgVersion) { + auditLogMsgs.add(msg); + } + + @Override + public void log(AuditLogMsgBuilder msgBldr) { + auditLogMsgs.add(msgBldr.build()); + } + + }; + + jettyLogger = new org.eclipse.jetty.util.log.Logger() { + public String getName() { return "TestLogger"; } + public void warn(String msg, Object... args) { + jettyLogMsgs.add(msg); + } + public void warn(Throwable thrown) { } + public void warn(String msg, Throwable thrown) {} + public void info(String msg, Object... args) {} + public void info(Throwable thrown) {} + public void info(String msg, Throwable thrown) {} + public boolean isDebugEnabled() { return true; } + public void setDebugEnabled(boolean enabled) {} + public void debug(String msg, Object... args) {} + public void debug(String msg, long value) {} + public void debug(Throwable thrown) {} + public void debug(String msg, Throwable thrown) {} + public org.eclipse.jetty.util.log.Logger getLogger(String name) { return this; } + public void ignore(Throwable ignored) {} + }; + + } + + @Test + public void testGetAuditLogMsgBuilder() { + AthenzRequestLog log = new AthenzRequestLog(); + AuditLogMsgBuilder msgBldr = log.getAuditLogMsgBuilder(mockRequest); + + String httpMethod = msgBldr.whatMethod(); + assertTrue(httpMethod.equals("GET"), httpMethod); + + String who = msgBldr.who(); + assertTrue(who.equals(CRED_IN_ERROR), who); + + String why = msgBldr.why(); + assertTrue(why.equals(AthenzRequestLog.AUDIT_LOG_CRED_REF), why); + + String api = msgBldr.whatApi(); + assertTrue(api.equals("/domain/jupiter/moons"), api); + + String when = msgBldr.when(); + Timestamp ts = Timestamp.fromMillis(currentTimeMillis); + assertTrue(ts.toString().equals(when), when); + + String remoteAddr = msgBldr.clientIp(); + assertTrue(remoteAddr.equals("11.16.20.15"), remoteAddr); + + String where = msgBldr.where(); + assertTrue(where.contains("FakeTestHostName"), where); + assertTrue(where.contains("4666"), where); + + String entity = msgBldr.whatEntity(); + assertTrue(entity.equals(AthenzRequestLog.AUDIT_CRED_ERROR_ATTR), entity); + + String credErr = msgBldr.whatDetails(); + assertTrue(credErr.equals("CRED_ERROR=(" + CRED_ATTR_ERROR + ");REMOTEUSER=(FakeUserId);"), credErr); + } + + @Test + public void testLogWithAuditLogger() { + + long bytesSent = 4 * 1024 * 1024; + String contentType = "text/html; charset=iso-8859-1"; + int responseCode = 401; + String reason = "bad credential"; + + Response mockResponse = Mockito.mock(Response.class); + Mockito.when(mockResponse.getLongContentLength()).thenReturn(bytesSent); + Mockito.when(mockResponse.getContentType()).thenReturn(contentType); + Mockito.when(mockResponse.getStatus()).thenReturn(responseCode); + Mockito.when(mockResponse.getReason()).thenReturn(reason); + + Timestamp ts = Timestamp.fromMillis(currentTimeMillis); + String when = ts.toString(); + + AthenzRequestLog log = new AthenzRequestLog("/dev/null", auditLogger); + log.log(mockRequest, mockResponse); + + for (String val: auditLogMsgs) { + assertTrue(val.contains("WHEN=(" + when), val); + assertTrue(val.contains("WHO=(" + CRED_IN_ERROR + ")"), val); + assertTrue(val.contains("WHY=(" + AthenzRequestLog.AUDIT_LOG_CRED_REF), val); + assertTrue(val.contains("WHERE=(server-ip=FakeTestHostName"), val); + assertTrue(val.contains("CLIENT-IP=(11.16.20.15)"), val); + assertTrue(val.contains("WHAT-method=(GET)"), val); + assertTrue(val.contains("WHAT-api=(/domain/jupiter/moons)"), val); + assertTrue(val.contains("WHAT-entity=(" + AthenzRequestLog.AUDIT_CRED_ERROR_ATTR), val); + assertTrue(val.contains("WHAT-details=(CRED_ERROR=(" + CRED_ATTR_ERROR + ");REMOTEUSER=(FakeUserId);"), val); + } + } + + @Test + public void testLogWithoutAuditLogger() throws Exception { + + long bytesSent = 4 * 1024 * 1024; + String contentType = "text/html; charset=iso-8859-1"; + int responseCode = 401; + String reason = "bad credential"; + + Response mockResponse = Mockito.mock(Response.class); + Mockito.when(mockResponse.getLongContentLength()).thenReturn(bytesSent); + Mockito.when(mockResponse.getContentType()).thenReturn(contentType); + Mockito.when(mockResponse.getStatus()).thenReturn(responseCode); + Mockito.when(mockResponse.getReason()).thenReturn(reason); + + Timestamp ts = Timestamp.fromMillis(currentTimeMillis); + String when = ts.toString(); + + AthenzRequestLog log = new AthenzRequestLog() { + protected org.eclipse.jetty.util.log.Logger getBackupLogger() { + return jettyLogger; + } + }; + log.log(mockRequest, mockResponse); + + for (String val: jettyLogMsgs) { + assertTrue(val.contains("WHEN=(" + when), val); + assertTrue(val.contains("WHO=(" + CRED_IN_ERROR + ")"), val); + assertTrue(val.contains("WHY=(" + AthenzRequestLog.AUDIT_LOG_CRED_REF), val); + assertTrue(val.contains("WHERE=(server-ip=FakeTestHostName"), val); + assertTrue(val.contains("CLIENT-IP=(11.16.20.15)"), val); + assertTrue(val.contains("WHAT-method=(GET)"), val); + assertTrue(val.contains("WHAT-api=(/domain/jupiter/moons)"), val); + assertTrue(val.contains("WHAT-entity=(" + AthenzRequestLog.AUDIT_CRED_ERROR_ATTR), val); + assertTrue(val.contains("WHAT-details=(CRED_ERROR=(" + CRED_ATTR_ERROR + ");REMOTEUSER=(FakeUserId);"), val); + } + } + + @Test + public void testGetAuditLogMsgBuilderFileNameOnly() { + AthenzRequestLog log = new AthenzRequestLog("/dev/null"); + assertNotNull(log); + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/AuditLogMsgBuilderTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/AuditLogMsgBuilderTest.java new file mode 100644 index 00000000000..9e3ecf3ad89 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/AuditLogMsgBuilderTest.java @@ -0,0 +1,664 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.log.impl; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.HashSet; +import java.util.regex.Pattern; + +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.yahoo.athenz.common.server.log.AuditLogFactory; +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.impl.DefaultAuditLogMsgBuilder; +import com.yahoo.rdl.*; + +/** + * Test all API of AuditLogMsgBuilder. + * Test the getMsgBuilder() API of AuditLogFactory. + */ +public class AuditLogMsgBuilderTest { + + private static final String ZMS_USER_DOMAIN = "athenz.user_domain"; + private static final String USER_DOMAIN = System.getProperty(ZMS_USER_DOMAIN, "user"); + + static String TOKEN_STR = "v=U1;d=" + USER_DOMAIN + ";n=roger;h=somehost.somecompany.com;a=666;t=1492;e=2493;s=signature;"; + + static Array assertsOrig = null; + static Array assertsNew = null; + + DefaultAuditLogMsgBuilder starter(final String whatApi) { + AuditLogMsgBuilder msgBldr = AuditLogFactory.getMsgBuilder(); + msgBldr.who(TOKEN_STR).when(Timestamp.fromCurrentTime()).clientIp("12.12.12.12").whatApi(whatApi); + return (DefaultAuditLogMsgBuilder)msgBldr; + } + + Struct setupEntity(final String [] keys, final Object [] vals) { + Struct entity = new Struct().with(keys[0], vals[0]); + for (int cnt = 1; cnt < keys.length; ++cnt) { + entity.with(keys[cnt], vals[cnt]); + } + return entity; + } + + @BeforeClass + public static synchronized void setUp() throws Exception { + Struct assert1 = new Struct().with("role", "worker").with("resource", "potatoes").with("action", "eat"); + Struct assert2 = new Struct().with("role", "worker").with("resource", "yams").with("action", "eat"); + Struct assert3 = new Struct().with("role", "child").with("resource", "cereal").with("action", "slurp"); + assertsOrig = new Array().with(assert1).with(assert2).with(assert3); + + Struct assert1n = new Struct().with("role", "worker").with("resource", "apples").with("action", "eat"); + Struct assert2n = new Struct().with("role", "worker").with("resource", "yams").with("action", "eat"); + Struct assert3n = new Struct().with("role", "child").with("resource", "cereal").with("action", "chomp"); + assertsNew = new Array().with(assert1n).with(assert2n).with(assert3n); + } + + @Test + public void testGetMsgBuilderClassName() { + String auditLogMsgBuilderClassName = "com.yahoo.athenz.common.server.log.impl.TestMsgBuilder"; + try { + AuditLogMsgBuilder msgBldr = AuditLogFactory.getMsgBuilder(auditLogMsgBuilderClassName); + String dataStr = "getMsgBuilder"; + msgBldr.whatApi(dataStr); + Assert.assertTrue(msgBldr.whatApi().equals(dataStr), "whatApi string=" + msgBldr.whatApi()); + dataStr = msgBldr.getClass().getName(); + Assert.assertTrue(dataStr.equals(auditLogMsgBuilderClassName), "classname=" + dataStr); + } catch (Exception exc) { + Assert.fail("Should have created the AuditLogMsgBuilder=TestMsgBuilder", exc); + } + } + + @Test + public void testWho() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWho"); + String dataStr = "me?"; + msgBldr.who(dataStr); + Assert.assertTrue(msgBldr.who().equals(dataStr), "who string=" + msgBldr.who()); + } + + @Test + public void testWhy() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhy"); + String dataStr = "not?"; + msgBldr.why(dataStr); + Assert.assertTrue(msgBldr.why().equals(dataStr), "why string=" + msgBldr.why()); + } + + @Test + public void testWhenTimestamp() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhenTimestamp"); + Timestamp ts = Timestamp.fromCurrentTime(); + msgBldr.when(ts); + String dataStr = ts.toString(); + Assert.assertTrue(msgBldr.when().equals(dataStr), "when string=" + msgBldr.when()); + } + + @Test + public void testWhenString() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhenString"); + String dataStr = "now?"; + msgBldr.when(dataStr); + Assert.assertTrue(msgBldr.when().equals(dataStr), "when string=" + msgBldr.when()); + } + + @Test + public void testClientIp() { + DefaultAuditLogMsgBuilder msgBldr = starter("testClientIp"); + String dataStr = "99.77.22.hup"; + msgBldr.clientIp(dataStr); + Assert.assertTrue(msgBldr.clientIp().equals(dataStr), "clientIp string=" + msgBldr.clientIp()); + } + + @Test + public void testWhereIp() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhereIp"); + String dataStr = "128.33.42.76"; + msgBldr.whereIp(dataStr); + Assert.assertTrue(msgBldr.where().contains("server-ip=" + dataStr), "whereIp string=" + msgBldr.where()); + } + + @Test + public void testWhereHttpsPort() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhereHttpsPort"); + String dataStr = "4443"; + msgBldr.whereHttpsPort(dataStr); + Assert.assertTrue(msgBldr.where().contains("server-https-port=" + dataStr), "whereHttpsPort string=" + msgBldr.where()); + } + + @Test + public void testWhereHttpPort() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhereHttpPort"); + String dataStr = "80"; + msgBldr.whereHttpPort(dataStr); + Assert.assertTrue(msgBldr.where().contains("server-http-port=" + dataStr), "whereHttpPort string=" + msgBldr.where()); + } + + @Test + public void testWhereIpPortHttpsPort() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhereIpPortHttpsPort"); + String ipStr = "128.33.42.76"; + msgBldr.whereIp(ipStr); + String httpsPortStr = "4443"; + msgBldr.whereHttpsPort(httpsPortStr); + String httpPortStr = "80"; + msgBldr.whereHttpPort(httpPortStr); + + Assert.assertTrue(msgBldr.where().contains("server-ip=" + ipStr), "where string=" + msgBldr.where()); + Assert.assertTrue(msgBldr.where().contains("server-https-port=" + httpsPortStr), "where string=" + msgBldr.where()); + Assert.assertTrue(msgBldr.where().contains("server-http-port=" + httpPortStr), "where string=" + msgBldr.where()); + } + + @Test + public void testWhatMethod() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhatMethod"); + String dataStr = "PUT"; + msgBldr.whatMethod(dataStr); + Assert.assertTrue(msgBldr.whatMethod().equals(dataStr), "whatMethod string=" + msgBldr.whatMethod()); + } + + @Test + public void testWhatApi() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhatApi"); + String dataStr = "putRole"; + msgBldr.whatApi(dataStr); + Assert.assertTrue(msgBldr.whatApi().equals(dataStr), "whatApi string=" + msgBldr.whatApi()); + } + + @Test + public void testWhatDomain() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhatDomain"); + String dataStr = "sys.auth"; + msgBldr.whatDomain(dataStr); + Assert.assertTrue(msgBldr.whatDomain().equals(dataStr), "whatDomain string=" + msgBldr.whatDomain()); + } + + @Test + public void testWhatEntity() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhatEntity"); + String dataStr = "readers"; + msgBldr.whatEntity(dataStr); + Assert.assertTrue(msgBldr.whatEntity().equals(dataStr), "whatEntity string=" + msgBldr.whatEntity()); + } + + /** + * Test method for {@link com.yahoo.athenz.common.server.log.impl.DefaultAuditLogMsgBuilder#buildDiffKeys(java.util.Set, com.yahoo.data.Struct, java.lang.StringBuilder)}. + */ + @Test + public void testBuildDiffKeys() { + String []keyNames = new String[] { "alpha", "beta" }; + String []vals = new String[] { "Apple", "Banana" }; + Struct entity = setupEntity(keyNames, vals); + Set keys = entity.keySet(); + + DefaultAuditLogMsgBuilder msgBldr = starter("testBuildDiffKeys"); + StringBuilder sb = new StringBuilder(""); + msgBldr.buildDiffKeys(keys, entity, sb); + String diff = sb.toString(); + Assert.assertTrue(diff.contains("alpha=\"Apple\""), "Test string=" + diff); + Assert.assertTrue(diff.contains("beta=\"Banana\""), "Test string=" + diff); + } + + @Test + public void testBuildDiffValueSet() { + + String []svals = new String[] { "Apple", "Banana", "PawPaw" }; + List vals = Arrays.asList( svals ); + Set valSet = new HashSet(vals); + + DefaultAuditLogMsgBuilder msgBldr = starter("testBuildDiffValueSet"); + StringBuilder sb = new StringBuilder(""); + msgBldr.buildDiffValueSet(valSet, sb); + String diff = sb.toString(); + Assert.assertTrue(diff.contains("Apple") && diff.contains("Banana") && diff.contains("PawPaw"), "Test string=" + diff); + } + + @Test + public void testBuildDiffArray() { + + DefaultAuditLogMsgBuilder msgBldr = starter("testBuildArrayDiff"); + StringBuilder addedSetSb = new StringBuilder(); + StringBuilder removedSetSb = new StringBuilder(); + msgBldr.buildDiffArray(assertsOrig, assertsNew, addedSetSb, removedSetSb); + + // Added-str={role: "child", resource: "cereal", action: "chomp"},{role: "worker", resource: "apples", action: "eat"} + // Removed-str={role: "worker", resource: "potatoes", action: "eat"},{role: "child", resource: "cereal", action: "slurp"} + boolean match = addedSetSb.indexOf("child") != -1 && + addedSetSb.indexOf("cereal") != -1 && addedSetSb.indexOf("chomp") != -1 && + addedSetSb.indexOf("slurp") == -1; + Assert.assertTrue(match, "Added-str for child=" + addedSetSb); + match = addedSetSb.indexOf("worker") != -1 && + addedSetSb.indexOf("apples") != -1 && addedSetSb.indexOf("eat") != -1 && + addedSetSb.indexOf("yams") == -1; + Assert.assertTrue(match, "Added-str for worker=" + addedSetSb); + + match = removedSetSb.indexOf("child") != -1 && + removedSetSb.indexOf("cereal") != -1 && removedSetSb.indexOf("slurp") != -1 && + removedSetSb.indexOf("chomp") == -1; + Assert.assertTrue(match, "Removed-str for child=" + removedSetSb); + match = removedSetSb.indexOf("worker") != -1 && + removedSetSb.indexOf("potatoes") != -1 && removedSetSb.indexOf("eat") != -1 && + removedSetSb.indexOf("yams") == -1; + Assert.assertTrue(match, "Removed-str for worker=" + removedSetSb); + } + + @Test + public void testWhatSubDetailsEntityStruct() { + // test Struct inside of Struct + // Entity from ZMS schema: contains 'name' as String and 'value' as Struct + DefaultAuditLogMsgBuilder msgBldr = starter("testWhatDetailsEntityStruct"); + Struct origValue = new Struct().with("field", "of_dreams").with("champ", "de_reve").with("same", "thing"); + Struct newValue = new Struct().with("field", "hockey").with("champ", "bailey").with("same", "thing"); + String [] origKeys = new String[] { "name", "value" }; + Object [] origVals = new Object[] { "policyAlpha", origValue }; + String [] newKeys = new String[] { "name", "value" }; + Object [] newVals = new Object[] { "policyAlpha", newValue }; + Struct origFields = setupEntity(origKeys, origVals); + Struct newFields = setupEntity(newKeys, newVals); + + String details = msgBldr.whatSubDetails("entity", origFields, newFields); + // details=entity=( + // CHANGED=( + // value=( + // CHANGED=( + // field=(FROM=(of_dreams);TO=(hockey)); + // champ=(FROM=(de_reve);TO=(bailey));); + // REMOVED=(null); + // ADDED=(null);)); + // REMOVED=(null); + // ADDED=(null);) + + int drindex = details.indexOf("field=(FROM=(of_dreams);TO=(hockey))"); + Assert.assertTrue(drindex != -1, "dreams-index=" + drindex + " details=" + details); + + int rindex = details.indexOf("champ=(FROM=(de_reve);TO=(bailey))"); + Assert.assertTrue(rindex != -1, "de_reve-index=" + rindex + " details=" + details); + } + + @Test + public void testWhatDetailsNoChanges() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhatDetails"); + String [] origKeys = new String[] { "alpha", "beta", "gamma" }; + String [] origVals = new String[] { "Aval", "Bval", "Gval" }; + String [] newKeys = new String[] { "alpha", "beta", "gamma" }; + String [] newVals = new String[] { "Aval", "Bval", "Gval" }; + Struct origFields = setupEntity(origKeys, origVals); + Struct newFields = setupEntity(newKeys, newVals); + msgBldr.whatDetails("Bsame", origFields, newFields); + + String msg = msgBldr.whatDetails(); + Assert.assertTrue(msg.contains("CHANGED=(null)"), "Test string=" + msg); + Assert.assertTrue(msg.contains("REMOVED=(null)"), "Test string=" + msg); + Assert.assertTrue(msg.contains("ADDED=(null)"), "Test string=" + msg); + } + + @Test + public void testWhatDetailsAdded() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhatDetails"); + String [] origKeys = new String[] { "alpha", "beta", "gamma" }; + String [] origVals = new String[] { "Aval", "Bval", "Gval" }; + String [] newKeys = new String[] { "alpha", "beta", "gamma", "delta" }; + String [] newVals = new String[] { "Aval", "Bval", "Gval", "Dval" }; + Struct origFields = setupEntity(origKeys, origVals); + Struct newFields = setupEntity(newKeys, newVals); + msgBldr.whatDetails("Bsame", origFields, newFields); + + String msg = msgBldr.whatDetails(); + Assert.assertTrue(msg.contains("CHANGED=(null)"), "Test string=" + msg); + Assert.assertTrue(msg.contains("REMOVED=(null)"), "Test string=" + msg); + Assert.assertTrue(msg.contains("ADDED=(delta=\"Dval\")"), "Test string=" + msg); + } + + @Test + public void testWhatDetailsRemoved() { + DefaultAuditLogMsgBuilder msgBldr = starter("testWhatDetails"); + String [] origKeys = new String[] { "alpha", "beta", "gamma" }; + String [] origVals = new String[] { "Aval", "Bval", "Gval" }; + String [] newKeys = new String[] { "alpha", "gamma" }; + String [] newVals = new String[] { "Aval", "Gval" }; + Struct origFields = setupEntity(origKeys, origVals); + Struct newFields = setupEntity(newKeys, newVals); + msgBldr.whatDetails("Bsame", origFields, newFields); + + String msg = msgBldr.whatDetails(); + Assert.assertTrue(msg.contains("CHANGED=(null)"), "Test string=" + msg); + Assert.assertTrue(msg.contains("REMOVED=(beta=\"Bval\")"), "Test string=" + msg); + Assert.assertTrue(msg.contains("ADDED=(null)"), "Test string=" + msg); + } + + /** + * Test method for {@link com.yahoo.athenz.common.server.log.impl.DefaultAuditLogMsgBuilder#whatDetails(java.lang.String, com.yahoo.data.Struct, com.yahoo.data.Struct)}. + */ + @Test + public void testWhatDetailsChangedRemovedAdded() { + AuditLogMsgBuilder msgBldr = starter("testWhatDetailsChangedRemovedAdded"); + + String [] origKeys = new String[] { "alpha", "beta", "gamma" }; + String [] origVals = new String[] { "Aval", "Bval", "Gval" }; + String [] newKeys = new String[] { "beta", "gamma", "delta" }; + String [] newVals = new String[] { "Bval", "Gammaval", "Dval" }; + Struct origFields = setupEntity(origKeys, origVals); + Struct newFields = setupEntity(newKeys, newVals); + msgBldr.whatDetails("Bsame", origFields, newFields); + + String msg = msgBldr.whatDetails(); + Assert.assertTrue(msg.contains("gamma=(FROM=(Gval);TO=(Gammaval))"), "Test string=" + msg); + Assert.assertTrue(msg.contains("REMOVED=(alpha=\"Aval\")"), "Test string=" + msg); + Assert.assertTrue(msg.contains("ADDED=(delta=\"Dval\")"), "Test string=" + msg); + } + + @Test + public void testWhatDetailsPolicyStructs() { + // test Array inside of Struct + // Policy from ZMS schema: contains 'name' as String, 'modified' as Timestamp, + // 'assertions' as Array of Struct + DefaultAuditLogMsgBuilder msgBldr = starter("testWhatDetailsPolicyStructs"); + Timestamp tsOrig = Timestamp.fromCurrentTime(); + long millis = tsOrig.millis() + 1000; + Timestamp tsNew = Timestamp.fromMillis(millis); + String [] origKeys = new String[] { "name", "modified", "assertions" }; + Object [] origVals = new Object[] { "policyAlpha", tsOrig, assertsOrig }; + String [] newKeys = new String[] { "name", "modified", "assertions" }; + Object [] newVals = new Object[] { "policyAlpha", tsNew, assertsNew }; + Struct origFields = setupEntity(origKeys, origVals); + Struct newFields = setupEntity(newKeys, newVals); + msgBldr.whatDetails("policies", origFields, newFields); + + String details = msgBldr.whatDetails(); + // details=policies= + // (CHANGED= + // (ADDED-VALUES= + // ({role: "child", resource: "cereal", action: "chomp"},{role: "worker", resource: "apples", action: "eat"}); + // REMOVED-VALUES=({role: "child", resource: "cereal", action: "slurp"},{role: "worker", resource: "potatoes", action: "eat"}); + // (FROM=(null); + // TO=(null);); + // REMOVED=(null); + // ADDED=(null);) + int aindex = details.indexOf("ADDED-VALUES=("); + Assert.assertTrue(aindex != -1, "details=" + details); + int rindex = details.indexOf("REMOVED-VALUES=("); + Assert.assertTrue(rindex != -1, "details=" + details); + + int addedVals = details.indexOf("chomp", aindex); + Assert.assertTrue(addedVals != -1 && addedVals < rindex, "details=" + details); + addedVals = details.indexOf("apples", aindex); + Assert.assertTrue(addedVals != -1 && addedVals < rindex, "details=" + details); + + addedVals = details.indexOf("slurp", rindex); + Assert.assertTrue(addedVals > rindex, "details=" + details); + addedVals = details.indexOf("potatoes", rindex); + Assert.assertTrue(addedVals > rindex, "details=" + details); + } + + /** + * Test method for {@link com.yahoo.athenz.common.server.log.impl.DefaultAuditLogMsgBuilder#build()}. + */ + @Test + public void testBuild() { + AuditLogMsgBuilder msgBldr = starter("testBuild"); + + String msg = msgBldr.build(); + Assert.assertTrue(msg.contains("WHAT-api=(testBuild)"), "Test string=" + msg); + } + + @Test + public void testBuildChangedRemovedAdded() { + AuditLogMsgBuilder msgBldr = starter("testBuildChangedRemovedAdded"); + String [] origKeys = new String[] { "alpha", "beta", "gamma", "epsilon" }; + String [] origVals = new String[] { "Aval", "Bval", "Gval", "Eval" }; + String [] newKeys = new String[] { "beta", "gamma", "delta" }; + String [] newVals = new String[] { "Bval", "Gammaval", "Dval" }; + Struct origFields = setupEntity(origKeys, origVals); + Struct newFields = setupEntity(newKeys, newVals); + msgBldr.whatDetails("Bsame", origFields, newFields); + + String msg = msgBldr.build(); + Assert.assertTrue(msg.contains("WHAT-api=(testBuildChangedRemovedAdded)"), "Test string=" + msg); + Assert.assertTrue(msg.contains("CHANGED=(gamma=(FROM=(Gval);TO=(Gammaval))"), "Test string=" + msg); + Assert.assertTrue(msg.contains("ADDED=(delta=\"Dval\")"), "Test string=" + msg); + + // order not important + boolean removedAlpha = msg.contains("REMOVED=(alpha=\"Aval\",epsilon=\"Eval\")"); + boolean removedEpsilon = msg.contains("REMOVED=(epsilon=\"Eval\",alpha=\"Aval\")"); + Assert.assertTrue(removedAlpha | removedEpsilon, "Test string=" + msg); + + } + + @Test + public void testFindChangedValues() { + String changed = "CHANGED=(org=(FROM=(testOrg);TO=(NewOrg));description=(FROM=(Test Domain1);TO=(Test2 Domain)););"; + DefaultAuditLogMsgBuilder msgBldr = new DefaultAuditLogMsgBuilder(); + + // get value without prefix + Array noPrefixValues = new Array(); + msgBldr.findChangedValues(noPrefixValues, changed, "FROM=(", "));", false); + Assert.assertTrue(noPrefixValues.size() == 2, "values size=" + noPrefixValues.size()); + String fromToVal1 = "org=(FROM=(testOrg);TO=(NewOrg"; + String fromToVal2 = "description=(FROM=(Test Domain1);TO=(Test2 Domain"; + String noPrefFromToVal1 = "FROM=(testOrg);TO=(NewOrg"; + String noPrefFromToVal2 = "FROM=(Test Domain1);TO=(Test2 Domain"; + for (int cnt = 0; cnt < noPrefixValues.size(); ++cnt) { + String fromToVal = (String) noPrefixValues.get(cnt); + Assert.assertFalse(fromToVal.contains(fromToVal1) || fromToVal.contains(fromToVal2), "From/To=" + fromToVal); + Assert.assertTrue(fromToVal.contains(noPrefFromToVal1) || fromToVal.contains(noPrefFromToVal2), "From/To=" + fromToVal); + } + + // get value with prefix + Array prefixValues = new Array(); + msgBldr.findChangedValues(prefixValues, changed, "FROM=(", "));", true); + Assert.assertTrue(prefixValues.size() == 2, "values size=" + prefixValues.size()); + for (int cnt = 0; cnt < prefixValues.size(); ++cnt) { + String fromToVal = (String) prefixValues.get(cnt); + Assert.assertTrue(fromToVal.contains(fromToVal1) || fromToVal.contains(fromToVal2), "From/To=" + fromToVal); + } + } + + @Test + public void testGetMatchedGroup() { + String GEN_FLD_PAT = "=\\(([^\\)]+)\\);.*"; + Pattern PAT_VERS = Pattern.compile(".*(VERS)" + GEN_FLD_PAT); + + String logMsg = "VERS=(test-0.1);WHEN=(2015-03-26T20:30:34.457Z);WHO=(who-name=testadminuser,who-domain=" + USER_DOMAIN + + ",who-yrn=" + USER_DOMAIN + ".testadminuser);WHY=(zmsjcltest);WHERE=(server-ip=somehost.somecompany.com,server-https-port=0,server-http-port=10080);CLIENT-IP=(127.0.0.1);WHAT-method=(PUT);WHAT-api=(putdomainmeta);WHAT-domain=(MetaDom1);WHAT-entity=(meta);WHAT-details=(meta-attrs=(CHANGED=(org=(FROM=(testOrg);TO=(NewOrg));description=(FROM=(Test Domain1);TO=(Test2 Domain)););REMOVED=(null);ADDED=(auditEnabled=true);));"; + CharSequence charSeq = logMsg.subSequence(0, logMsg.length()); + DefaultAuditLogMsgBuilder msgBldr = new DefaultAuditLogMsgBuilder(); + String group = msgBldr.getMatchedGroup(PAT_VERS, 2, charSeq); + Assert.assertNotNull(group); + Assert.assertEquals(group, "test-0.1"); + + // choose bad group number + group = msgBldr.getMatchedGroup(PAT_VERS, 3, charSeq); + Assert.assertNull(group); + + // choose group number 0 - whole string + group = msgBldr.getMatchedGroup(PAT_VERS, 0, charSeq); + Assert.assertNotNull(group); + Assert.assertEquals(group, logMsg); + } + + @Test + public void testParse() { + String logMsg = "WHEN=(2015-03-26T20:30:34.457Z);WHO=(who-name=testadminuser,who-domain=" + USER_DOMAIN + + ",who-yrn=" + USER_DOMAIN + ".testadminuser);WHY=(zmsjcltest);WHERE=(server-ip=somehost.somecompany.com,server-https-port=0,server-http-port=10080);CLIENT-IP=(127.0.0.1);WHAT-method=(PUT);WHAT-api=(putdomainmeta);WHAT-domain=(MetaDom1);WHAT-entity=(meta);WHAT-details=(meta-attrs=(CHANGED=(metattrs=(ADDED-VALUES=(\"" + USER_DOMAIN + ".doe\"));metattrs=(REMOVED-VALUES=(\"" + USER_DOMAIN + ".dough\"));org=(FROM=(testOrg);TO=(NewOrg));description=(FROM=(Test Domain1);TO=(Test2 Domain)););REMOVED=(null);ADDED=(auditEnabled=true);));"; + AuditLogMsgBuilder msgBldr = new DefaultAuditLogMsgBuilder(); + Struct parsed = msgBldr.parse(logMsg); + Assert.assertNotNull(parsed); + Assert.assertTrue(parsed.containsKey("WHEN")); + String val = parsed.getString("WHEN"); + Assert.assertTrue(val.contains("2015-03-26T20:30:34.457Z")); + Assert.assertTrue(parsed.containsKey("WHO")); + val = parsed.getString("WHO"); + Assert.assertTrue(val.contains("who-name=testadminuser,who-domain=" + USER_DOMAIN + ",who-yrn=" + USER_DOMAIN + ".testadminuser")); + Assert.assertTrue(parsed.containsKey("WHY")); + val = parsed.getString("WHY"); + Assert.assertTrue(val.contains("zmsjcltest")); + Assert.assertTrue(parsed.containsKey("WHERE")); + val = parsed.getString("WHERE"); + Assert.assertTrue(val.contains("server-ip=somehost.somecompany.com,server-https-port=0,server-http-port=10080")); + Assert.assertTrue(parsed.containsKey("CLIENT-IP")); + val = parsed.getString("CLIENT-IP"); + Assert.assertTrue(val.contains("127.0.0.1")); + Assert.assertTrue(parsed.containsKey("WHAT-method")); + val = parsed.getString("WHAT-method"); + Assert.assertTrue(val.contains("PUT")); + Assert.assertTrue(parsed.containsKey("WHAT-api")); + val = parsed.getString("WHAT-api"); + Assert.assertTrue(val.contains("putdomainmeta")); + Assert.assertTrue(parsed.containsKey("WHAT-domain")); + val = parsed.getString("WHAT-domain"); + Assert.assertTrue(val.contains("MetaDom1")); + Assert.assertTrue(parsed.containsKey("WHAT-entity")); + val = parsed.getString("WHAT-entity"); + Assert.assertTrue(val.contains("meta")); + + Assert.assertTrue(parsed.containsKey("WHAT-details")); + Struct details = parsed.getStruct("WHAT-details"); + Assert.assertNotNull(details); + Assert.assertTrue(details.containsKey("CHANGED")); + val = details.getString("CHANGED"); + Assert.assertTrue(val.contains("CHANGED=(metattrs=(ADDED-VALUES=(\"" + USER_DOMAIN + ".doe\"));metattrs=(REMOVED-VALUES=(\"" + USER_DOMAIN + ".dough\"));org=(FROM=(testOrg);TO=(NewOrg))"), val); + + Assert.assertTrue(details.containsKey("REMOVED")); + val = details.getString("REMOVED"); + Assert.assertTrue(val.contains("null")); + Assert.assertTrue(details.containsKey("ADDED")); + val = details.getString("ADDED"); + Assert.assertTrue(val.contains("auditEnabled=true")); + + Assert.assertTrue(details.containsKey("ADDED-VALUES")); + Array values = (Array) details.get("ADDED-VALUES"); + Assert.assertNotNull(values); + Assert.assertTrue(values.size() == 1, "values size=" + values.size()); + for (int cnt = 0; cnt < values.size(); ++cnt) { + String addedVal = (String) values.get(cnt); + Assert.assertTrue(addedVal.contains("" + USER_DOMAIN + ".doe")); + } + + Assert.assertTrue(details.containsKey("REMOVED-VALUES")); + values = (Array) details.get("REMOVED-VALUES"); + Assert.assertNotNull(values); + Assert.assertTrue(values.size() == 1, "values size=" + values.size()); + for (int cnt = 0; cnt < values.size(); ++cnt) { + String removedVal = (String) values.get(cnt); + Assert.assertTrue(removedVal.contains("" + USER_DOMAIN + ".dough")); + } + + Assert.assertTrue(details.containsKey("FROM-TO-VALUES")); + values = (Array) details.get("FROM-TO-VALUES"); + Assert.assertNotNull(values); + Assert.assertTrue(values.size() == 2, "values size=" + values.size()); + String fromToVal1 = "org=(FROM=(testOrg);TO=(NewOrg"; + String fromToVal2 = "description=(FROM=(Test Domain1);TO=(Test2 Domain"; + for (int cnt = 0; cnt < values.size(); ++cnt) { + String fromToVal = (String) values.get(cnt); + Assert.assertTrue(fromToVal.contains(fromToVal1) || fromToVal.contains(fromToVal2), "From/To=" + fromToVal); + } + } + + @Test + public void testParseChangesAddedRemovedValues() { + String logMsg = "VERS=(athenz-def-1.0);WHEN=(2015-04-02T17:09:31.387Z);WHO=(v=U1;d=user;n=jdoe);WHY=(audittest);WHERE=(server-ip=localhost,server-https-port=0,server-http-port=10080);CLIENT-IP=(MOCKCLIENT_HOST_NAME);WHAT-method=(PUT);WHAT-api=(putpolicy);WHAT-domain=(CrossDomainAccessDom1);WHAT-entity=(tenancy.coretech.storage.writer);WHAT-details=(policy-attrs=(CHANGED=(modified=(FROM=(2015-04-02T17:09:31.354Z);TO=(2015-04-02T17:09:31.387Z));assertions=(ADDED-VALUES=({role: \"CrossDomainAccessDom1:role.writer\", action: \"ASSUME_ROLE\", effect: \"ALLOW\", resource: \"coretech:role.storage.tenant.CrossDomainAccessDom1.writer\"}));assertions=(REMOVED-VALUES=({role: \"CrossDomainAccessDom1:role.admin\", action: \"ASSUME_ROLE\", resource: \"coretech:role.storage.tenant.CrossDomainAccessDom1.writer\"})););REMOVED=(null);ADDED=(null);));"; + + AuditLogMsgBuilder msgBldr = new DefaultAuditLogMsgBuilder(); + Struct parsed = msgBldr.parse(logMsg); + Assert.assertNotNull(parsed); + Assert.assertTrue(parsed.containsKey("WHEN")); + String val = parsed.getString("WHEN"); + Assert.assertTrue(val.contains("2015-04-02T17:09:31.387Z")); + + Assert.assertTrue(parsed.containsKey("WHAT-details")); + Struct details = parsed.getStruct("WHAT-details"); + Assert.assertNotNull(details); + Assert.assertTrue(details.containsKey("CHANGED")); + val = details.getString("CHANGED"); + Assert.assertTrue(val.contains("CHANGED=(modified=(FROM=(2015-04-02T17:09:31.354Z);"), val); + + Assert.assertTrue(details.containsKey("REMOVED-VALUES")); + Array values = (Array) details.get("REMOVED-VALUES"); + Assert.assertNotNull(values); + Assert.assertTrue(values.size() == 1, "values size=" + values.size()); + for (int cnt = 0; cnt < values.size(); ++cnt) { + String removedVal = (String) values.get(cnt); + Assert.assertTrue(removedVal.contains("assertions=(REMOVED-VALUES=({role: \"CrossDomainAccessDom1:role.admin\", action: \"ASSUME_ROLE\", resource: \"coretech:role.storage.tenant.CrossDomainAccessDom1.writer\"})"), val); + } + + Assert.assertTrue(details.containsKey("ADDED-VALUES")); + values = (Array) details.get("ADDED-VALUES"); + Assert.assertNotNull(values); + Assert.assertTrue(values.size() == 1, "values size=" + values.size()); + for (int cnt = 0; cnt < values.size(); ++cnt) { + String addedVal = (String) values.get(cnt); + Assert.assertTrue(addedVal.contains("assertions=(ADDED-VALUES=({role: \"CrossDomainAccessDom1:role.writer\", action: \"ASSUME_ROLE\", effect: \"ALLOW\", resource: \"coretech:role.storage.tenant.CrossDomainAccessDom1.writer\"})"), val); + } + } + + @Test + public void testParseChangesAddedRemovedEmbedded() { + String logMsg = "VERS=(athenz-def-1.0);WHEN=(2015-04-02T18:30:58.451Z);WHO=(v=U1;d=user;n=jdoe);WHY=(audittest);WHERE=(server-ip=localhost,server-https-port=0,server-http-port=10080);CLIENT-IP=(MOCKCLIENT_HOST_NAME);WHAT-method=(PUT);WHAT-api=(puttenantroles);WHAT-domain=(coretech);WHAT-entity=(storage:AddTenancyDom1);WHAT-details=(AddTenancyDom1=(CHANGED=(role=(CHANGED=(null);role=(REMOVED=(null));role=(ADDED=(storage.tenant.AddTenancyDom1.reader={trust: \"AddTenancyDom1\", modified: \"2015-04-02T18:30:58.451Z\", name: \"coretech:role.storage.tenant.AddTenancyDom1.reader\"},storage.tenant.AddTenancyDom1.admin={trust: \"AddTenancyDom1\", modified: \"2015-04-02T18:30:58.451Z\", name: \"coretech:role.storage.tenant.AddTenancyDom1.admin\"},storage.tenant.AddTenancyDom1.writer={trust: \"AddTenancyDom1\", modified: \"2015-04-02T18:30:58.451Z\", name: \"coretech:role.storage.tenant.AddTenancyDom1.writer\"}));policy=(CHANGED=(null);policy=(REMOVED=(null));policy=(ADDED=(storage.tenant.AddTenancyDom1.reader={assertions: [{role: \"coretech:role.storage.tenant.AddTenancyDom1.reader\", action: \"READ\", resource: \"coretech:service.storage.tenant.AddTenancyDom1.*\"}], modified: \"2015-04-02T18:30:58.451Z\", name: \"coretech:policy.storage.tenant.AddTenancyDom1.reader\"},storage.tenant.AddTenancyDom1.admin={assertions: [{role: \"coretech:role.storage.tenant.AddTenancyDom1.admin\", action: \"*\", resource: \"coretech:service.storage.tenant.AddTenancyDom1.*\"}], modified: \"2015-04-02T18:30:58.451Z\", name: \"coretech:policy.storage.tenant.AddTenancyDom1.admin\"},storage.tenant.AddTenancyDom1.writer={assertions: [{role: \"coretech:role.storage.tenant.AddTenancyDom1.writer\", action: \"WRITE\", resource: \"coretech:service.storage.tenant.AddTenancyDom1.*\"}], modified: \"2015-04-02T18:30:58.451Z\", name: \"coretech:policy.storage.tenant.AddTenancyDom1.writer\"})););AddTenancyDom1=(REMOVED=(null));AddTenancyDom1=(ADDED=(null)););"; + + AuditLogMsgBuilder msgBldr = new DefaultAuditLogMsgBuilder(); + Struct parsed = msgBldr.parse(logMsg); + Assert.assertNotNull(parsed); + Assert.assertTrue(parsed.containsKey("WHO")); + String val = parsed.getString("WHO"); + Assert.assertTrue(val.contains("v=U1;d=user;n=jdoe")); + + Assert.assertTrue(parsed.containsKey("WHAT-details")); + Struct details = parsed.getStruct("WHAT-details"); + Assert.assertNotNull(details); + + Assert.assertTrue(details.containsKey("REMOVED")); + val = details.getString("REMOVED"); + Assert.assertTrue(val.contains("null")); + Assert.assertTrue(details.containsKey("ADDED")); + val = details.getString("ADDED"); + Assert.assertTrue(val.contains("null")); + + Assert.assertTrue(details.containsKey("CHANGED")); + val = details.getString("CHANGED"); + Assert.assertTrue(val.contains("CHANGED=(role=(CHANGED=(null);"), val); + + // get all the embedded ADDED/REMOVED from changes + Assert.assertTrue(details.containsKey("EMBEDDED-REMOVED")); + Array values = (Array) details.get("EMBEDDED-REMOVED"); + Assert.assertNotNull(values); + Assert.assertTrue(values.size() == 2, "values size=" + values.size()); + String removedEmbeddedVal1 = "role=(REMOVED=(null)"; + String removedEmbeddedVal2 = "policy=(REMOVED=(null)"; + for (int cnt = 0; cnt < values.size(); ++cnt) { + String removedVal = (String) values.get(cnt); + boolean matched = removedVal.contains(removedEmbeddedVal1) || removedVal.contains(removedEmbeddedVal2); + Assert.assertTrue(matched, removedVal); + } + + Assert.assertTrue(details.containsKey("EMBEDDED-ADDED")); + values = (Array) details.get("EMBEDDED-ADDED"); + Assert.assertNotNull(values); + Assert.assertTrue(values.size() == 2, "values size=" + values.size()); + String addedEmbeddedVal1 = "role=(ADDED=(storage.tenant.AddTenancyDom1.reader={trust: \"AddTenancyDom1\","; + String addedEmbeddedVal2 = "policy=(ADDED=(storage.tenant.AddTenancyDom1.reader={assertions: [{role: \"coretech:role.storage.tenant.AddTenancyDom1.reader\", action: \"READ\","; + for (int cnt = 0; cnt < values.size(); ++cnt) { + String addedVal = (String) values.get(cnt); + boolean matched = addedVal.contains(addedEmbeddedVal1) || addedVal.contains(addedEmbeddedVal2); + Assert.assertTrue(matched, addedVal); + } + + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/AuditLoggerTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/AuditLoggerTest.java new file mode 100644 index 00000000000..3ee78cb980e --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/AuditLoggerTest.java @@ -0,0 +1,106 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.log.impl; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.yahoo.athenz.common.server.log.AuditLogFactory; +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.AuditLogger; + +import org.testng.annotations.BeforeClass; + + +public class AuditLoggerTest { + + protected static AuditLogger auditLogger; + + final static String MSGVERS = "VERS=(test);"; + + @BeforeClass + public static synchronized void setUp() throws Exception { + auditLogger = new AuditLogger() { + @Override + public void log(String msg, String msgVersion) { + Assert.assertTrue(msg != null); + } + + @Override + public void log(AuditLogMsgBuilder msgBldr) { + Assert.assertTrue(msgBldr.build() != null); + } + + }; + } + + @Test + public void testLogFactoryDefault() { + AuditLogger logger = AuditLogFactory.getLogger(); + logger.log("Default logger succeeds", MSGVERS); + } + + @Test + public void testLogFactoryString() { + String auditLoggerClassName = "com.yahoo.athenz.common.server.log.impl.TestLogger"; + try { + AuditLogger logger = AuditLogFactory.getLogger(auditLoggerClassName); + logger.log("TestLogger succeeds", null); + String dataStr = logger.getClass().getName(); + Assert.assertTrue(dataStr.equals(auditLoggerClassName), "classname=" + dataStr); + } catch (Exception exc) { + Assert.fail("Should have created the Logger=TestLogger with default constructor", exc); + } + } + + @Test + public void testLogFactoryFalseParam() { + String auditLoggerClassName = "com.yahoo.athenz.common.server.log.impl.TestLogger"; + Object param = new Boolean(false); + try { + AuditLogger logger = AuditLogFactory.getLogger(auditLoggerClassName, param); + logger.log("TestLogger succeeds", MSGVERS); + String dataStr = logger.getClass().getName(); + Assert.assertTrue(dataStr.equals(auditLoggerClassName), "classname=" + dataStr); + } catch (Exception exc) { + Assert.fail("Should have created the Logger=TestLogger with constructor taking param=" + param, exc); + } + } + + @Test + public void testLogFactoryTrueParam() { + String auditLoggerClassName = "com.yahoo.athenz.common.server.log.impl.TestLogger"; + Object param = new Boolean(true); + try { + AuditLogger logger = AuditLogFactory.getLogger(auditLoggerClassName, param); + logger.log("TestLogger should not succeed", null); + Assert.fail("Should have thrown exception, Logger=TestLogger for param=" + param); + } catch (Exception exc) { + Assert.assertTrue(exc.getMessage().contains("TestLogger should not succeed"), "Should have thrown exception, Logger=TestLogger with constructor taking param=" + param); + } + } + + @Test + public void testLogString() { + auditLogger.log("testLog", null); + } + + @Test + public void testLogMsgBuilder() { + AuditLogMsgBuilder msgBldr = AuditLogFactory.getMsgBuilder(); + auditLogger.log(msgBldr); + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/TestLogger.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/TestLogger.java new file mode 100644 index 00000000000..e5495b96d91 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/TestLogger.java @@ -0,0 +1,57 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.log.impl; + +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.AuditLogger; + +/** + * Used for testing AuditLogFactory. + */ +public class TestLogger implements AuditLogger { + + Boolean throwException = false; + + public TestLogger() { + } + + public TestLogger(Boolean throwExc) { + if (throwExc != null) { + throwException = throwExc; + } + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogger#log(java.lang.String, java.lang.String) + */ + @Override + public void log(String logMsg, String msgVersionTag) { + if (throwException.booleanValue()) { + throw new RuntimeException(logMsg); + } + } + + /* (non-Javadoc) + * @see com.yahoo.athenz.common.server.log.AuditLogger#log(com.yahoo.athenz.common.server.log.AuditLogMsgBuilder) + */ + @Override + public void log(AuditLogMsgBuilder msgBldr) { + if (throwException.booleanValue()) { + throw new RuntimeException(msgBldr.build()); + } + } + +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/TestMsgBuilder.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/TestMsgBuilder.java new file mode 100644 index 00000000000..5bfef5bea5a --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/log/impl/TestMsgBuilder.java @@ -0,0 +1,24 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.log.impl; + +import com.yahoo.athenz.common.server.log.impl.DefaultAuditLogMsgBuilder; + +/** + * Test implementation for AuditLogMsgBuilder. + */ +public class TestMsgBuilder extends DefaultAuditLogMsgBuilder { +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/HttpContainerTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/HttpContainerTest.java new file mode 100644 index 00000000000..dad3ed7c9d7 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/HttpContainerTest.java @@ -0,0 +1,77 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.rest; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Authorizer; + +import org.glassfish.hk2.utilities.binding.AbstractBinder; + +import org.mockito.Mockito; + +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +public class HttpContainerTest { + + @Test + public void testAuthority() { + Authority authority = Mockito.mock(Authority.class); + HttpContainer extend = new HttpContainer(); + assertNotNull(extend.authority(authority)); + } + + @Test + public void testAuthorizer() { + Authorizer authorizer = Mockito.mock(Authorizer.class); + HttpContainer extend = new HttpContainer(); + assertNotNull(extend.authorizer(authorizer)); + } + + @Test + public void testAbstractBinder() { + AbstractBinder abstractBinder = Mockito.mock(AbstractBinder.class); + HttpContainer extend = new HttpContainer(); + assertNotNull(extend.delegate(abstractBinder)); + } + + @Test + public void testSetBanner() { + HttpContainer extend = new HttpContainer(); + extend.setBanner("hoge"); + assertEquals(extend.banner, "hoge"); + } + + @Test + public void testGetServer() { + HttpContainer extend = new HttpContainer(); + assertNull(extend.getServer()); + } + + @Test + public void testGetHandlers() { + HttpContainer extend = new HttpContainer(); + assertNull(extend.getHandlers()); + } + + @Test + public void testStop() { + HttpContainer extend = new HttpContainer(); + extend.createServer(20); + assertNotNull(extend.getHandlers()); + extend.stop(); + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/HttpTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/HttpTest.java new file mode 100644 index 00000000000..28d5ca150f4 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/HttpTest.java @@ -0,0 +1,147 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.rest; + +import javax.servlet.http.HttpServletRequest; + +import org.mockito.Mockito; + +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Authorizer; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.Authority.CredSource; + +public class HttpTest { + + @Test + public void testAuthenticatingCredentialsNull() { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + assertNull(Http.authenticatingCredentials(httpServletRequest, null)); + } + + @Test + public void testAuthenticatingCredentials() { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Http.AuthorityList authorities = new Http.AuthorityList(); + Authority authority = Mockito.mock(Authority.class); + Mockito.when(authority.getHeader()).thenReturn("hogehoge"); + authorities.add(authority); + assertNull(Http.authenticatingCredentials(httpServletRequest, authorities)); + } + + @Test + public void testAuthoritiesNotNull() { + Http.AuthorityList authorities = new Http.AuthorityList(); + assertNotNull(authorities.getAuthorities()); + } + + @Test + public void testAuthenticateInternalServerError() throws Exception { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + try { + Http.authenticate(httpServletRequest, null); + } catch (ResourceException expected) { + assertEquals(expected.getCode(), 500); + } + } + + @Test + public void testAuthenticateCertificate() throws Exception { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Http.AuthorityList authorities = new Http.AuthorityList(); + Authority authority = Mockito.mock(Authority.class); + Mockito.when(authority.getCredSource()).thenReturn(CredSource.CERTIFICATE); + authorities.add(authority); + try { + Http.authenticate(httpServletRequest, authorities); + } catch (ResourceException expected) { + assertEquals(expected.getCode(), 401); + } + } + + @Test + public void testAuthenticateHeader() throws Exception { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Http.AuthorityList authorities = new Http.AuthorityList(); + Authority authority = Mockito.mock(Authority.class); + Mockito.when(authority.getCredSource()).thenReturn(CredSource.HEADER); + Mockito.when(authority.getHeader()).thenReturn("Cookie.hogehoge"); + authorities.add(authority); + try { + Http.authenticate(httpServletRequest, authorities); + } catch (ResourceException expected) { + assertEquals(expected.getCode(), 401); + } + } + + @Test + public void testAuthenticatedUserInvalidCredentials() throws Exception { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Http.AuthorityList authorities = new Http.AuthorityList(); + try { + Http.authenticatedUser(httpServletRequest, authorities); + } catch (ResourceException expected) { + assertEquals(expected.getCode(), 401); + } + } + + @Test + public void testAuthorizedUserUserInvalidCredentials() throws Exception { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Authorizer authorizer = Mockito.mock(Authorizer.class); + Http.AuthorityList authorities = new Http.AuthorityList(); + try { + Http.authorizedUser(httpServletRequest, authorities, authorizer, "action", null, null); + } catch (ResourceException expected) { + assertEquals(expected.getCode(), 401); + } + } + + @Test + public void testAuthorizedBadRequest() throws Exception { + Authorizer authorizer = Mockito.mock(Authorizer.class); + Principal principal = Mockito.mock(Principal.class); + try { + Http.authorize(authorizer, principal, "action", null, null); + } catch (ResourceException expected) { + assertEquals(expected.getCode(), 400); + } + } + + @Test + public void testAuthorizedInternalServerError() throws Exception { + Principal principal = Mockito.mock(Principal.class); + try { + Http.authorize(null, principal, "action", "resource", null); + } catch (ResourceException expected) { + assertEquals(expected.getCode(), 500); + } + } + + @Test + public void testAuthorizedForbidden() throws Exception { + Authorizer authorizer = Mockito.mock(Authorizer.class); + Principal principal = Mockito.mock(Principal.class); + try { + Http.authorize(authorizer, principal, "action", "resource", null); + } catch (ResourceException expected) { + assertEquals(expected.getCode(), 403); + } + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/ResourceContextTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/ResourceContextTest.java new file mode 100644 index 00000000000..67d7cb90d9d --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/ResourceContextTest.java @@ -0,0 +1,66 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.rest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.mockito.Mockito; + +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Authorizer; + +public class ResourceContextTest { + + @Test + public void testResourceContext() { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + HttpServletResponse httpServletResponse = Mockito.mock(HttpServletResponse.class); + Authorizer authorizer = Mockito.mock(Authorizer.class); + Http.AuthorityList authorities = new Http.AuthorityList(); + ResourceContext context = new ResourceContext(httpServletRequest, httpServletResponse, authorities, authorizer); + assertEquals(context.request(), httpServletRequest); + assertEquals(context.response(), httpServletResponse); + assertNull(context.principal()); + } + + @Test + public void testAuthenticate() { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + HttpServletResponse httpServletResponse = Mockito.mock(HttpServletResponse.class); + Authorizer authorizer = Mockito.mock(Authorizer.class); + Http.AuthorityList authorities = new Http.AuthorityList(); + ResourceContext context = new ResourceContext(httpServletRequest, httpServletResponse, authorities, authorizer); + context.checked = true; + assertNull(context.authenticate()); + } + + @Test + public void testAuthorize() { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + HttpServletResponse httpServletResponse = Mockito.mock(HttpServletResponse.class); + Authorizer authorizer = Mockito.mock(Authorizer.class); + Http.AuthorityList authorities = new Http.AuthorityList(); + ResourceContext context = new ResourceContext(httpServletRequest, httpServletResponse, authorities, authorizer); + try { + context.authorize("action", "resource", "domain"); + } catch (ResourceException expected) { + assertEquals(expected.getCode(), 401); + } + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/ResourceExceptionTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/ResourceExceptionTest.java new file mode 100644 index 00000000000..a6924638175 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/ResourceExceptionTest.java @@ -0,0 +1,81 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.rest; + +import static org.testng.Assert.*; +import org.testng.annotations.*; + +public class ResourceExceptionTest { + + @Test + public void testCodeForSymbol() { + assertEquals(ResourceException.codeForSymbol("ok"), 200); + assertEquals(ResourceException.codeForSymbol("CREATED"), 201); + assertEquals(ResourceException.codeForSymbol("ACCEPTED"), 202); + assertEquals(ResourceException.codeForSymbol("NO_CONTENT"), 204); + assertEquals(ResourceException.codeForSymbol("MOVED_PERMANENTLY"), 301); + assertEquals(ResourceException.codeForSymbol("FOUND"), 302); + assertEquals(ResourceException.codeForSymbol("SEE_OTHER"), 303); + assertEquals(ResourceException.codeForSymbol("NOT_MODIFIED"), 304); + assertEquals(ResourceException.codeForSymbol("TEMPORARY_REDIRECT"), 307); + assertEquals(ResourceException.codeForSymbol("BAD_REQUEST"), 400); + assertEquals(ResourceException.codeForSymbol("FORBIDDEN"), 403); + assertEquals(ResourceException.codeForSymbol("UNAUTHORIZED"), 401); + assertEquals(ResourceException.codeForSymbol("NOT_FOUND"), 404); + assertEquals(ResourceException.codeForSymbol("CONFLICT"), 409); + assertEquals(ResourceException.codeForSymbol("GONE"), 410); + assertEquals(ResourceException.codeForSymbol("PRECONDITION_FAILED"), 412); + assertEquals(ResourceException.codeForSymbol("UNSUPPORTED_MEDIA_TYPE"), 415); + assertEquals(ResourceException.codeForSymbol("INTERNAL_SERVER_ERROR"), 500); + assertEquals(ResourceException.codeForSymbol("NOT_IMPLEMENTED"), 501); + assertEquals(ResourceException.codeForSymbol("SERVICE_UNAVAILABLE"), 503); + assertEquals(ResourceException.codeForSymbol("UNAUTHORIZED"), 401); + assertEquals(ResourceException.codeForSymbol("1111"), 1111); + } + + @Test + public void testSymbolForCode() { + assertEquals(ResourceException.symbolForCode(200), "OK"); + assertEquals(ResourceException.symbolForCode(201), "CREATED"); + assertEquals(ResourceException.symbolForCode(202), "ACCEPTED"); + assertEquals(ResourceException.symbolForCode(204), "NO_CONTENT"); + assertEquals(ResourceException.symbolForCode(301), "MOVED_PERMANENTLY"); + assertEquals(ResourceException.symbolForCode(302), "FOUND"); + assertEquals(ResourceException.symbolForCode(303), "SEE_OTHER"); + assertEquals(ResourceException.symbolForCode(304), "NOT_MODIFIED"); + assertEquals(ResourceException.symbolForCode(307), "TEMPORARY_REDIRECT"); + assertEquals(ResourceException.symbolForCode(400), "BAD_REQUEST"); + assertEquals(ResourceException.symbolForCode(403), "FORBIDDEN"); + assertEquals(ResourceException.symbolForCode(401), "UNAUTHORIZED"); + assertEquals(ResourceException.symbolForCode(404), "NOT_FOUND"); + assertEquals(ResourceException.symbolForCode(409), "CONFLICT"); + assertEquals(ResourceException.symbolForCode(410), "GONE"); + assertEquals(ResourceException.symbolForCode(412), "PRECONDITION_FAILED"); + assertEquals(ResourceException.symbolForCode(415), "UNSUPPORTED_MEDIA_TYPE"); + assertEquals(ResourceException.symbolForCode(500), "INTERNAL_SERVER_ERROR"); + assertEquals(ResourceException.symbolForCode(501), "NOT_IMPLEMENTED"); + assertEquals(ResourceException.symbolForCode(503), "SERVICE_UNAVAILABLE"); + assertEquals(ResourceException.symbolForCode(401), "UNAUTHORIZED"); + assertNull(ResourceException.symbolForCode(1111)); + } + + @Test + public void testResourceException() { + ResourceException exception = new ResourceException(200); + assertEquals(exception.getCode(), 200); + assertEquals(exception.getData(), "OK"); + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/RestCoreResourceConfigTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/RestCoreResourceConfigTest.java new file mode 100644 index 00000000000..74276aa66d8 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/rest/RestCoreResourceConfigTest.java @@ -0,0 +1,56 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.rest; + +import java.util.HashSet; + +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import com.yahoo.athenz.common.server.rest.ResourceException; +import com.yahoo.athenz.common.server.rest.RestCoreResourceConfig; + +import static org.testng.Assert.*; + +/* + * This is used to setup the application configuration for authorities, + * authorizer, supported content-providers, and delegate. + */ +public class RestCoreResourceConfigTest { + + @SuppressWarnings({ "rawtypes", "unchecked", "unused" }) + @Test + public void testRestCoreResourceConfigBadRequest() { + HashSet hashSet = Mockito.mock(HashSet.class); + try { + RestCoreResourceConfig restCoreResourceConfig = new RestCoreResourceConfig(null, hashSet); + } catch (ResourceException expected) { + assertEquals(expected.getCode(), 400); + } + } + + @SuppressWarnings("unused") + @Test + public void testsetSingletonsNull() { + HashSet> resources = new HashSet>(); + HashSet singletons = new HashSet(); + try { + RestCoreResourceConfig restCoreResourceConfig = new RestCoreResourceConfig(resources, singletons); + } catch (ResourceException expected) { + assertEquals(expected.getCode(), 400); + } + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/util/ServletRequestUtilTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/util/ServletRequestUtilTest.java new file mode 100644 index 00000000000..88c5f5478b0 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/util/ServletRequestUtilTest.java @@ -0,0 +1,65 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.util; + +import javax.servlet.http.HttpServletRequest; + +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import com.yahoo.athenz.common.server.util.ServletRequestUtil; + +import static org.testng.Assert.*; + +public class ServletRequestUtilTest { + + @Test + public void testGetRemoteAddressNoLoopBack() { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(httpServletRequest.getRemoteAddr()).thenReturn("1.2.3.4"); + assertEquals(ServletRequestUtil.getRemoteAddress(httpServletRequest), "1.2.3.4"); + } + + @Test + public void testGetRemoteAddressNull() { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(httpServletRequest.getRemoteAddr()).thenReturn(null); + assertNull(ServletRequestUtil.getRemoteAddress(httpServletRequest)); + } + + @Test + public void testGetRemoteAddressLoopBackNoXFF() { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(httpServletRequest.getRemoteAddr()).thenReturn("127.0.0.1"); + assertEquals(ServletRequestUtil.getRemoteAddress(httpServletRequest), "127.0.0.1"); + } + + @Test + public void testGetRemoteAddressLoopBackSingleXFF() { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(httpServletRequest.getRemoteAddr()).thenReturn("127.0.0.1"); + Mockito.when(httpServletRequest.getHeader("X-Forwarded-For")).thenReturn("1.2.3.4"); + assertEquals(ServletRequestUtil.getRemoteAddress(httpServletRequest), "1.2.3.4"); + } + + @Test + public void testGetRemoteAddressLoopBackMultipleXFF() { + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(httpServletRequest.getRemoteAddr()).thenReturn("127.0.0.1"); + Mockito.when(httpServletRequest.getHeader("X-Forwarded-For")).thenReturn("1.2.3.4, 1.3.4.5, 1.4.5.6"); + assertEquals(ServletRequestUtil.getRemoteAddress(httpServletRequest), "1.4.5.6"); + } +} diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/util/StringUtilsTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/util/StringUtilsTest.java new file mode 100644 index 00000000000..c1468d39dd8 --- /dev/null +++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/server/util/StringUtilsTest.java @@ -0,0 +1,48 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.common.server.util; + +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +import com.yahoo.athenz.common.server.util.StringUtils; + +public class StringUtilsTest { + + @Test + public void testRemoveLeadingAndTrailingQuotes() { + + assertEquals(StringUtils.removeLeadingAndTrailingQuotes("abc"), "abc"); + assertEquals(StringUtils.removeLeadingAndTrailingQuotes("\"abc"), "abc"); + assertEquals(StringUtils.removeLeadingAndTrailingQuotes("abc\""), "abc"); + assertEquals(StringUtils.removeLeadingAndTrailingQuotes("\"abc\""), "abc"); + assertEquals(StringUtils.removeLeadingAndTrailingQuotes("\"a\"bc\""), "a\"bc"); + } + + @Test + public void testPatternFromGlob() { + assertEquals("^abc$", StringUtils.patternFromGlob("abc")); + assertEquals("^abc.*$", StringUtils.patternFromGlob("abc*")); + assertEquals("^abc.$", StringUtils.patternFromGlob("abc?")); + assertEquals("^.*abc.$", StringUtils.patternFromGlob("*abc?")); + assertEquals("^abc\\.abc:.*$", StringUtils.patternFromGlob("abc.abc:*")); + assertEquals("^ab\\[a-c]c$", StringUtils.patternFromGlob("ab[a-c]c")); + assertEquals("^ab.*\\.\\(\\)\\^\\$c$", StringUtils.patternFromGlob("ab*.()^$c")); + assertEquals("^abc\\\\test\\\\$", StringUtils.patternFromGlob("abc\\test\\")); + assertEquals("^ab\\{\\|c\\+$", StringUtils.patternFromGlob("ab{|c+")); + assertEquals("^\\^\\$\\[\\(\\)\\\\\\+\\{\\..*.\\|$", StringUtils.patternFromGlob("^$[()\\+{.*?|")); + } +} diff --git a/libs/java/server_common/src/test/resources/logback.xml b/libs/java/server_common/src/test/resources/logback.xml new file mode 100644 index 00000000000..c4a0357b8d5 --- /dev/null +++ b/libs/java/server_common/src/test/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + System.out + + %-4relative [%thread] %-5level %class - %msg%n + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000000..ef26677812f --- /dev/null +++ b/pom.xml @@ -0,0 +1,193 @@ + + + + + 4.0.0 + + com.yahoo.athenz + athenz + pom + 1.0-SNAPSHOT + + AthenZ + + AthenZ is a set of services and libraries supporting + role-based authorization for provisioning and configuration + (centralized authorization) use cases as well as serving/runtime + (decentralized authorization) use cases. + + https://github.com/yahoo/athenz + + + Yahoo Inc. + https://www.yahoo.com/ + + 2016 + + + + Yahoo Inc. + https://www.yahoo.com/ + + + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + https://github.com/yahoo/athenz + scm:git:https://github.com/yahoo/athenz.git + scm:git:ssh://git@github.com:yahoo/athenz.git + + + + Github + https://github.com/yahoo/athenz/issues + + + + Travis + https://travis-ci.org/yahoo/athenz + + + + 9.3.15.v20161220 + 2.23.2 + 2.5.4 + 1.55 + UTF-8 + UTF-8 + + + + core/zms + core/zts + libs/java/auth_core + libs/java/client_common + libs/java/server_common + clients/java/sia + clients/java/zms + clients/java/zts + clients/java/zpe + servers/zms + servers/zts + utils/zpe_policy_updater + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + + + + + + + + + com.yahoo.rdl + rdl-java + 1.4.8-2 + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + + + + + org.slf4j + slf4j-api + 1.7.5 + + + ch.qos.logback + logback-classic + 1.1.3 + test + + + org.testng + testng + 6.3.1 + test + + + org.mockito + mockito-all + 1.9.5 + test + + + + + + + false + + bintray-yahoo-maven + bintray + http://yahoo.bintray.com/maven + + + + + + coverage + + + coverage + + + + + + com.atlassian.maven.plugins + maven-clover2-plugin + 4.0.4 + + 1.8 + ${HOME}/license/clover.license + + + + + + + + diff --git a/servers/zms/README.md b/servers/zms/README.md new file mode 100644 index 00000000000..e375da1931d --- /dev/null +++ b/servers/zms/README.md @@ -0,0 +1,14 @@ +ZMS Server +======================= + +ZMS is where domains, roles, and policies are defined. This is Athenz’s centralized authorization system and is likely part of a larger management system. In addition to allowing CRUD operations on the basic entities, ZMS provides an API to replicate the entities, per domain, to ZTS. It also can directly support the access check, both for internal management system checks, as well as a simple centralized deployment. + +ZMS is the source of truth for domains, roles, and policies for centralized authorization. ZMS supports a centralized call to check if a principal has access to a resource. Because ZMS supports service identities, ZMS can authenticate services. + +For centralized authorization, ZMS may be the only Athenz subsystem that you need to interact with. + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/servers/zms/conf/authorized_services.json b/servers/zms/conf/authorized_services.json new file mode 100644 index 00000000000..00814ed301b --- /dev/null +++ b/servers/zms/conf/authorized_services.json @@ -0,0 +1,37 @@ +{ + "services" : { + "athenz.ci": { + "allowedOperations": [ + { "name": "putrole" }, + { "name": "putmembership", + "items": { + "role" : [ + "athenz_dev_test_role", + "athenz_eng_test_role" + ] + } + } + ] + }, + "athenz.ui": { + "allowedOperations": [ + { "name":"putmembership" }, + { "name":"deletemembership" }, + { "name":"postuserdomain" }, + { "name":"postsubdomain" }, + { "name":"deletesubdomain" }, + { "name":"deleterole" }, + { "name":"putrole" }, + { "name":"putpolicy" }, + { "name":"deletepolicy" }, + { "name":"putassertion" }, + { "name":"deleteassertion" }, + { "name":"putserviceidentity" }, + { "name":"deleteserviceidentity" }, + { "name":"putpublickeyentry" }, + { "name":"deletepublickeyentry" }, + { "name":"putdomainmeta" } + ] + } + } +} diff --git a/servers/zms/conf/container_settings b/servers/zms/conf/container_settings new file mode 100644 index 00000000000..d5efcc8803c --- /dev/null +++ b/servers/zms/conf/container_settings @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +# ** list of authorities for authenticating principals +CONTAINER_AUTHORITY_CLASSES="com.yahoo.athenz.auth.impl.PrincipalAuthority,com.yahoo.athenz.auth.impl.UserAuthority" + +# ** admin user for new installations - defaults to current user +CONTAINER_ADMINUSER="user.${USER}" + +# ** private/public key pair for zms instance - must be generated +# ** using with default key id of 0 +CONTAINER_PRIVKEY="${ROOT}/var/zms_server/keys/zms_private.pem" +CONTAINER_PUBKEY="${ROOT}/var/zms_server/keys/zms_public.pem" +CONTAINER_PRIVKEY_ID="0" + +# ** default ports for zms server. http support is disabled +# ** https support enabled - must provide certificate for server +CONTAINER_TLS_PORT="4443" +CONTAINER_PORT="0" + +# ** logback configuration file +CONTAINER_LOG_CONFIG="${ROOT}/conf/zms_server/logback.xml" + +# ** read only mode +CONTAINER_READ_ONLY_MODE="false" + +# ** authorized service and solution template file names +CONTAINER_AUTHZ_SERVICE_FNAME="${ROOT}/conf/zms_server/authorized_services.json" +CONTAINER_SOLUTION_TEMPLATES_FNAME="${ROOT}/conf/zms_server/solution_templates.json" + +# ** setup the keystore in pkcs12 format that includes our +# ** server's private key and x509 cert +CONTAINER_SSL_KEYSTORE="file://${ROOT}/var/zms_server/certs/zms_keystore.pkcs12" +CONTAINER_SSL_KEYSTORE_TYPE="PKCS12" +CONTAINER_SSL_KEYSTORE_PASSWORD="athenz" + +# ** truststore settings +# CONTAINER_SSL_TRUSTSTORE= +# CONTAINER_SSL_TRUSTSTORE_TYPE= +# CONTAINER_SSL_TRUSTSTORE_PASSWORD= + +# CONTAINER_SSL_KEYMANAGER_PASSWORD= +# CONTAINER_SSL_EXCLUDED_CIPHER_SUITES= +# CONTAINER_SSL_EXCLUDED_PROTOCOLS= + +# ** pam service name for user authority if we don't want to +# ** use the default login service +# CONTAINER_AUTH_PAM_SERVICE="login" + +# ** timeout sttings +# CONTAINER_CONFLICT_RETRY_TIMEOUT= +# CONTAINER_RETRY_DELAY_TIMEOUT= +# CONTAINER_USER_TOKEN_TIMEOUT= +# CONTAINER_SIGNED_POLICY_TIMEOUT= + +# ** virtual domain support +# CONTAINER_VIRTUAL_DOMAIN_SUPPORT= +# CONTAINER_VIRTUAL_DOMAIN_LIMIT= + +# ** max supported domain name length +# CONTAINER_DOMAIN_NAME_MAX_LEN=128 + +# ** jdbc settings +# CONTAINER_JDBCSTORE=jdbc:mysql://localhost:3306/zms" +# CONTAINER_JDBCUSER=zms_admin" +# CONTAINER_JDBCPASSWD= +# CONTAINER_JDBCTABLE="zms_root" +# CONTAINER_DBPOOL_MAX_TOTAL= +# CONTAINER_DBPOOL_MAX_IDLE= +# CONTAINER_DBPOOL_MIN_IDLE= +# CONTAINER_DBPOOL_MAX_WAIT= +# CONTAINER_DBPOOL_EVICT_IDLE_TIMEOUT= +# CONTAINER_DBPOOL_EVICT_IDLE_INTERVAL= +# CONTAINER_DBPOOL_MAX_TTL= + +# ** access log settings +# CONTAINER_ACCESS_LOG_DIR="logs/zms_server" +# CONTAINER_ACCESS_LOG_NAME= +# CONTAINER_ACCESS_LOG_RETAIN_DAYS= diff --git a/servers/zms/conf/logback.xml b/servers/zms/conf/logback.xml new file mode 100755 index 00000000000..50be080d710 --- /dev/null +++ b/servers/zms/conf/logback.xml @@ -0,0 +1,49 @@ + + + + + + + + + ${LOG_DIR}/server.log + true + + + ${LOG_DIR}/server.%d.log + 7 + true + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n + + + + + + + ${LOG_DIR}/audit.log + true + + + ${LOG_DIR}/audit.%d.log + 30 + true + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n + + + + + + + + + + + + + diff --git a/servers/zms/conf/solution_templates.json b/servers/zms/conf/solution_templates.json new file mode 100644 index 00000000000..9b312a7c483 --- /dev/null +++ b/servers/zms/conf/solution_templates.json @@ -0,0 +1,61 @@ +{ + "templates" : { + "user_provisioning": { + "roles": [ + { + "name": "_domain_:role.user", + "modified": "1970-01-01T00:00:00.000Z" + }, + { + "name": "_domain_:role.superuser", + "modified": "1970-01-01T00:00:00.000Z" + }, + { + "name": "_domain_:role.builders", + "members": [ "sys.builder" ], + "modified": "1970-01-01T00:00:00.000Z" + } + ], + "policies": [ + { + "name": "_domain_:policy.user", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:node.*", + "role": "_domain_:role.user", + "action": "node_user" + } + ] + }, + { + "name": "_domain_:policy.superuser", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:node.*", + "role": "_domain_:role.superuser", + "action": "node_sudo" + } + ] + }, + { + "name": "_domain_:policy.builders", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:build", + "role": "_domain_:role.builders", + "action": "read" + }, + { + "resource": "_domain_:build", + "role": "_domain_:role.builders", + "action": "delete" + } + ] + } + ] + } + } +} diff --git a/servers/zms/pom.xml b/servers/zms/pom.xml new file mode 100644 index 00000000000..36b1a0efd73 --- /dev/null +++ b/servers/zms/pom.xml @@ -0,0 +1,226 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../pom.xml + + + zms_server + zms_server + Athenz ZMS Server + jar + + + + ${project.groupId} + zms_core + ${project.parent.version} + + + ${project.groupId} + server_common + ${project.parent.version} + + + ${project.groupId} + client_common + ${project.parent.version} + + + ${project.groupId} + auth_core + ${project.parent.version} + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey.version} + + + com.fasterxml.jackson.core + jackson-annotations + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + commons-daemon + commons-daemon + 1.0.15 + + + mysql + mysql-connector-java + 5.1.6 + + + com.google.guava + guava + 18.0 + + + org.apache.commons + commons-dbcp2 + 2.0.1 + + + org.apache.commons + commons-pool2 + 2.2 + + + commons-logging + commons-logging + 1.1.3 + + + org.glassfish.jersey.containers + jersey-container-servlet + ${jersey.version} + + + org.eclipse.jetty + jetty-servlet + ${jetty.version} + + + org.eclipse.jetty + jetty-rewrite + ${jetty.version} + + + org.eclipse.jetty + jetty-util + ${jetty.version} + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + org.eclipse.jetty + jetty-security + ${jetty.version} + + + org.xerial + sqlite-jdbc + 3.7.2 + + + org.bouncycastle + bcprov-jdk15on + ${bouncycastle.version} + + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + + + ch.qos.logback + logback-classic + 1.1.3 + + + + + + coverage + + + coverage + + + + + + com.atlassian.maven.plugins + maven-clover2-plugin + 4.0.4 + + 1.8 + ${HOME}/license/clover.license + + + **/provider/ProviderClient.java + **/provider/ResourceError.java + **/provider/ResourceException.java + **/zms/GetSignedDomainsResult.java + **/zms/ResourceContext.java + **/zms/ResourceError.java + **/zms/ResourceException.java + **/zms/ZMSHandler.java + **/zms/ZMSResources.java + + + + + + + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.1.1 + + + + exec + + generate-sources + + + + bash + + scripts/make_stubs.sh + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.0 + + + copy-dependencies + package + + copy-dependencies + + + runtime + + + + + + + + diff --git a/servers/zms/schema/zms_server.mwb b/servers/zms/schema/zms_server.mwb new file mode 100644 index 0000000000000000000000000000000000000000..01cedee5fae2a971498eaf011c78e73f700ba2d2 GIT binary patch literal 22654 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 Axc~qF literal 0 HcmV?d00001 diff --git a/servers/zms/schema/zms_server.sql b/servers/zms/schema/zms_server.sql new file mode 100644 index 00000000000..aecf4ff7cc9 --- /dev/null +++ b/servers/zms/schema/zms_server.sql @@ -0,0 +1,266 @@ +-- MySQL Script generated by MySQL Workbench +-- Tue May 17 17:40:32 2016 +-- Model: New Model Version: 1.0 +-- MySQL Workbench Forward Engineering + +SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; +SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES'; + +-- ----------------------------------------------------- +-- Schema zms_server +-- ----------------------------------------------------- + +-- ----------------------------------------------------- +-- Schema zms_server +-- ----------------------------------------------------- +CREATE SCHEMA IF NOT EXISTS `zms_server` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ; +USE `zms_server` ; + +-- ----------------------------------------------------- +-- Table `zms_server`.`domain` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`domain` ( + `domain_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(512) NOT NULL, + `description` VARCHAR(4096) NOT NULL DEFAULT '', + `org` VARCHAR(1024) NOT NULL DEFAULT '', + `uuid` VARCHAR(128) NOT NULL DEFAULT '', + `enabled` TINYINT(1) NOT NULL DEFAULT 1, + `audit_enabled` TINYINT(1) NOT NULL DEFAULT 0, + `ypm_id` INT UNSIGNED NOT NULL DEFAULT 0, + `account` VARCHAR(128) NOT NULL DEFAULT '', + `modified` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + `created` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`domain_id`), + UNIQUE INDEX `uq_name` (`name` ASC), + INDEX `idx_modified` (`modified` ASC), + INDEX `idx_account` (`account` ASC), + INDEX `idx_ypmid` (`ypm_id` ASC)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`role` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`role` ( + `role_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `domain_id` INT UNSIGNED NOT NULL, + `name` VARCHAR(512) NOT NULL, + `trust` VARCHAR(512) NOT NULL DEFAULT '', + `modified` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + `created` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`role_id`), + UNIQUE INDEX `uq_domain_role` (`domain_id` ASC, `name` ASC), + CONSTRAINT `fk_role_domain` + FOREIGN KEY (`domain_id`) + REFERENCES `zms_server`.`domain` (`domain_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`principal` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`principal` ( + `principal_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(512) NOT NULL, + PRIMARY KEY (`principal_id`), + UNIQUE INDEX `uq_name` (`name` ASC)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`policy` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`policy` ( + `policy_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `domain_id` INT UNSIGNED NOT NULL, + `name` VARCHAR(512) NOT NULL, + `modified` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + `created` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`policy_id`), + INDEX `fk_policy_domain_idx` (`domain_id` ASC), + UNIQUE INDEX `uq_domain_policy` (`name` ASC, `domain_id` ASC), + CONSTRAINT `fk_policy_domain` + FOREIGN KEY (`domain_id`) + REFERENCES `zms_server`.`domain` (`domain_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`assertion` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`assertion` ( + `assertion_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `policy_id` INT UNSIGNED NOT NULL, + `role` VARCHAR(512) NOT NULL, + `resource` VARCHAR(1024) NOT NULL, + `action` VARCHAR(128) NOT NULL, + `effect` VARCHAR(16) NOT NULL DEFAULT 'ALLOW', + PRIMARY KEY (`assertion_id`), + INDEX `fk_assertion_policy_idx` (`policy_id` ASC), + INDEX `idx_role` (`role` ASC), + INDEX `idx_resource` (`resource` ASC), + INDEX `idx_action` (`action` ASC), + INDEX `idx_effect` (`effect` ASC), + CONSTRAINT `fk_policy_assertion_policy` + FOREIGN KEY (`policy_id`) + REFERENCES `zms_server`.`policy` (`policy_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`service` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`service` ( + `service_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `domain_id` INT UNSIGNED NOT NULL, + `name` VARCHAR(512) NOT NULL, + `provider_endpoint` VARCHAR(512) NOT NULL DEFAULT '', + `executable` VARCHAR(256) NOT NULL DEFAULT '', + `svc_user` VARCHAR(256) NOT NULL DEFAULT '', + `svc_group` VARCHAR(256) NOT NULL DEFAULT '', + `modified` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + `created` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`service_id`), + INDEX `fk_service_domain_idx` (`domain_id` ASC), + UNIQUE INDEX `uq_domain_service` (`name` ASC, `domain_id` ASC), + CONSTRAINT `fk_service_domain` + FOREIGN KEY (`domain_id`) + REFERENCES `zms_server`.`domain` (`domain_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`public_key` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`public_key` ( + `service_id` INT UNSIGNED NOT NULL, + `key_id` VARCHAR(128) NOT NULL, + `key_value` TEXT(65535) NOT NULL, + PRIMARY KEY (`service_id`, `key_id`), + CONSTRAINT `fk_service_public_key_service` + FOREIGN KEY (`service_id`) + REFERENCES `zms_server`.`service` (`service_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`host` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`host` ( + `host_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(512) NOT NULL, + PRIMARY KEY (`host_id`), + UNIQUE INDEX `uq_name` (`name` ASC)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`service_host` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`service_host` ( + `service_id` INT UNSIGNED NOT NULL, + `host_id` INT UNSIGNED NOT NULL, + INDEX `idx_host` (`host_id` ASC, `service_id` ASC), + PRIMARY KEY (`service_id`, `host_id`), + CONSTRAINT `fk_service_host_host` + FOREIGN KEY (`host_id`) + REFERENCES `zms_server`.`host` (`host_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION, + CONSTRAINT `fk_service_host_service` + FOREIGN KEY (`service_id`) + REFERENCES `zms_server`.`service` (`service_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`entity` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`entity` ( + `name` VARCHAR(256) NOT NULL, + `domain_id` INT UNSIGNED NOT NULL, + `value` TEXT(65536) NOT NULL, + `modified` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + `created` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`name`, `domain_id`), + CONSTRAINT `fk_entity_domain` + FOREIGN KEY (`domain_id`) + REFERENCES `zms_server`.`domain` (`domain_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`role_member` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`role_member` ( + `role_id` INT UNSIGNED NOT NULL, + `principal_id` INT UNSIGNED NOT NULL, + PRIMARY KEY (`role_id`, `principal_id`), + INDEX `idx_principal` (`principal_id` ASC, `role_id` ASC), + CONSTRAINT `fk_role_member_role` + FOREIGN KEY (`role_id`) + REFERENCES `zms_server`.`role` (`role_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION, + CONSTRAINT `fk_role_member_principal` + FOREIGN KEY (`principal_id`) + REFERENCES `zms_server`.`principal` (`principal_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`domain_template` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`domain_template` ( + `domain_id` INT UNSIGNED NOT NULL, + `template` VARCHAR(64) NOT NULL, + UNIQUE INDEX `uq_domain_template` (`template` ASC, `domain_id` ASC), + PRIMARY KEY (`domain_id`, `template`), + CONSTRAINT `fk_domain_template_domain` + FOREIGN KEY (`domain_id`) + REFERENCES `zms_server`.`domain` (`domain_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `zms_server`.`role_audit_log` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `zms_server`.`role_audit_log` ( + `role_id` INT UNSIGNED NOT NULL, + `admin` VARCHAR(512) NOT NULL, + `member` VARCHAR(512) NOT NULL, + `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `action` VARCHAR(32) NOT NULL, + `audit_ref` VARCHAR(512) NOT NULL, + INDEX `fk_role_audit_log_role_id_idx` (`role_id` ASC), + CONSTRAINT `fk_role_audit_log_role` + FOREIGN KEY (`role_id`) + REFERENCES `zms_server`.`role` (`role_id`) + ON DELETE CASCADE + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +SET SQL_MODE=@OLD_SQL_MODE; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/servers/zms/scripts/make_stubs.sh b/servers/zms/scripts/make_stubs.sh new file mode 100755 index 00000000000..f83647a3d9e --- /dev/null +++ b/servers/zms/scripts/make_stubs.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# If the zms_core dependency has been updated, then this script should be run +# manually to pick up the latest rdl to generate the appropriate server stubs. + +# Note this script is dependent on the rdl utility. +# go get github.com/ardielle/ardielle-tools/... + +command -v rdl >/dev/null 2>&1 || { + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "SOURCE WARNING"; + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "Please install rdl utility: go get github.com/ardielle/ardielle-tools/..."; + echo >&2 "Skipping source generation..."; + exit 0; +} + +RDL_ZMS_FILE=src/main/rdl/ZMS.rdl +RDL_PROVIDER_FILE=../../core/zms/src/main/rdl/Provider.rdl + +echo "Generate the server stubs" +rdl -s generate -o src/main/java java-server $RDL_ZMS_FILE + +echo "Generate the provier client library" +rdl -s generate -o src/main/java java-client $RDL_PROVIDER_FILE + +echo "Removing not needed ZMS Server file..." +rm src/main/java/com/yahoo/athenz/zms/ZMSServer.java + +# Copyright 2016 Yahoo Inc. +# Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. diff --git a/servers/zms/scripts/zms_debug.sh b/servers/zms/scripts/zms_debug.sh new file mode 100755 index 00000000000..6474828e845 --- /dev/null +++ b/servers/zms/scripts/zms_debug.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# setup ZMS settings for debug run + +export ZMS_OPTS="${ZMS_OPTS} -Dlogback.configurationFile=src/test/resources/logback.xml" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.domain_admin=user.$USER,user.zms_test_admin,user.user_admin" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.authority_classes=com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority,com.yahoo.athenz.common.server.debug.DebugUserAuthority,com.yahoo.athenz.common.server.debug.DebugRoleAuthority,com.yahoo.athenz.common.server.debug.DebugKerberosAuthority" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.home=./" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.jdbcstore=./" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.port=4080" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.private_key_store_class=com.yahoo.athenz.zms.pkey.file.FilePrivateKeyStoreFactory" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.privatekey=src/test/resources/zms_private.pem" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.privatekey.version=0" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.publickey=src/test/resources/zms_public.pem" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.access_log_dir=./zms_logs" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.enable_stats=false" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.virtual_domain_support=true" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.virtual_domain_limit=0" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.solution_templates_fname=src/test/resources/solution_templates.json" +export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.authz_service_fname=src/test/resources/authorized_services.json" + +if [ "$1" == "-ssl" ]; then + export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.tls_port=4443" + export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.ssl_key_store_password=password" + export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.ssl_key_store=file://$HOME/.keystore" + export ZMS_OPTS="${ZMS_OPTS} -Dathenz.zms.ssl_key_store_type=PKCS12" +fi + + +# make sure the access log directory exits + +mkdir -p ./zms_logs + +mvn exec:java -Dexec.mainClass="com.yahoo.athenz.zms.ZMS" ${ZMS_OPTS} + +# Copyright 2016 Yahoo Inc. +# Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. diff --git a/servers/zms/scripts/zms_start.sh b/servers/zms/scripts/zms_start.sh new file mode 100644 index 00000000000..98b07c16e08 --- /dev/null +++ b/servers/zms/scripts/zms_start.sh @@ -0,0 +1,196 @@ +#!/bin/bash + +if [ -z "${ROOT}" ]; then + ROOT="/home/athenz" +fi + +CONTAINER_NAME=zms_server + +# pick up our service settings which should override +# some of the default values set by the code +if [ ! -f ${ROOT}/conf/${CONTAINER_NAME}/container_settings ]; then + echo "Unable to find container settings: ${ROOT}/conf/${CONTAINER_NAME}/container_settings aborting" + exit -1 +fi + +. "${ROOT}/conf/${CONTAINER_NAME}/container_settings" + +CONTAINER_RUN_PATH=${ROOT}/var/run/zms_server +CONTAINER_CLASSPATH=${ROOT}/lib/jar/zms_server*.jar:${ROOT}/lib/jars/* +CONTAINER_BOOTSTRAP_CLASS=com.yahoo.athenz.zms.ZMS + +if [ "${CONTAINER_READ_ONLY_MODE}" == "true" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.read_only_mode=true" +fi + +if [ "${CONTAINER_VIRTUAL_DOMAIN_SUPPORT}" == "true" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.virtual_domain_support=true" +fi + +if [ "x${CONTAINER_VIRTUAL_DOMAIN_LIMIT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.virtual_domain_limit=${CONTAINER_VIRTUAL_DOMAIN_LIMIT}" +fi + +export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.port=${CONTAINER_PORT}" +export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.tls_port=${CONTAINER_TLS_PORT}" +export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.logs=${ROOT}/logs/${CONTAINER_NAME}" + +if [ "x${CONTAINER_LOG_CONFIG}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dlogback.configurationFile=${CONTAINER_LOG_CONFIG}" +fi + +if [ "x{$CONTAINER_PRIVKEY}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.privatekey=${CONTAINER_PRIVKEY}" +fi + +if [ "x${CONTAINER_PRIVKEY_ID}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.privatekey_id=${CONTAINER_PRIVKEY_ID}" +fi + +if [ "x${CONTAINER_PUBKEY}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.publickey=${CONTAINER_PUBKEY}" +fi + +if [ "x${CONTAINER_HOSTNAME}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.hostname=${CONTAINER_HOSTNAME}" +fi + +if [ "x${CONTAINER_CONFLICT_RETRY_TIMEOUT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.conflict_retry_timeout=${CONTAINER_CONFLICT_RETRY_TIMEOUT}" +fi + +if [ "x${CONTAINER_RETRY_DELAY_TIMEOUT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.retry_delay_timeout=${CONTAINER_RETRY_DELAY_TIMEOUT}" +fi + +if [ "x${CONTAINER_USER_TOKEN_TIMEOUT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.user_token_timeout=${CONTAINER_USER_TOKEN_TIMEOUT}" +fi + +if [ "x${CONTAINER_SIGNED_POLICY_TIMEOUT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.signed_policy_timeout=${CONTAINER_SIGNED_POLICY_TIMEOUT}" +fi + +if [ "x${CONTAINER_ADMINUSER}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.domain_admin=${CONTAINER_ADMINUSER}" +fi + +if [ "x${CONTAINER_JDBCSTORE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.jdbcstore=${CONTAINER_JDBCSTORE}" +fi + +if [ "x${CONTAINER_JDBCUSER}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.jdbc_user=${CONTAINER_JDBCUSER}" +fi + +if [ "x${CONTAINER_JDBCPASSWD}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.jdbc_password=${CONTAINER_JDBCPASSWD}" +fi + +if [ "x${CONTAINER_JDBCTABLE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.zms_dbtable=${CONTAINER_JDBCTABLE}" +fi + +if [ "x${CONTAINER_DBPOOL_MAX_TOTAL}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.db_pool_max_total=${CONTAINER_DBPOOL_MAX_TOTAL}" +fi + +if [ "x${CONTAINER_DBPOOL_MAX_IDLE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.db_pool_max_idle=${CONTAINER_DBPOOL_MAX_IDLE}" +fi + +if [ "x${CONTAINER_DBPOOL_MIN_IDLE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.db_pool_min_idle=${CONTAINER_DBPOOL_MIN_IDLE}" +fi + +if [ "x${CONTAINER_DBPOOL_MAX_WAIT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.db_pool_max_wait=${CONTAINER_DBPOOL_MAX_WAIT}" +fi + +if [ "x${CONTAINER_DBPOOL_EVICT_IDLE_TIMEOUT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.db_pool_evict_idle_timeout=${CONTAINER_DBPOOL_EVICT_IDLE_TIMEOUT}" +fi + +if [ "x${CONTAINER_DBPOOL_EVICT_IDLE_INTERVAL}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.db_pool_evict_idle_interval=${CONTAINER_DBPOOL_EVICT_IDLE_INTERVAL}" +fi + +if [ "x${CONTAINER_DBPOOL_MAX_TTL}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.db_pool_max_ttl=${CONTAINER_DBPOOL_MAX_TTL}" +fi + +if [ "x${CONTAINER_SSL_KEYSTORE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.ssl_key_store=${CONTAINER_SSL_KEYSTORE}" +fi + +if [ "x${CONTAINER_SSL_KEYSTORE_TYPE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.ssl_key_store_type=${CONTAINER_SSL_KEYSTORE_TYPE}" +fi + +if [ "x${CONTAINER_SSL_KEYSTORE_PASSWORD}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.ssl_key_store_password=${CONTAINER_SSL_KEYSTORE_PASSWORD}" +fi + +if [ "x${CONTAINER_SSL_TRUSTSTORE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.ssl_trust_store=${CONTAINER_SSL_TRUSTSTORE}" +fi + +if [ "x${CONTAINER_SSL_TRUSTSTORE_TYPE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.ssl_trust_store_type=${CONTAINER_SSL_TRUSTSTORE_TYPE}" +fi + +if [ "x${CONTAINER_SSL_TRUSTSTORE_PASSWORD}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.ssl_trust_store_password=${CONTAINER_SSL_TRUSTSTORE_PASSWORD}" +fi + +if [ "x${CONTAINER_SSL_KEYMANAGER_PASSWORD}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.ssl_key_manager_password=${CONTAINER_SSL_KEYMANAGER_PASSWORD}" +fi + +if [ "x${CONTAINER_SSL_EXCLUDED_CIPHER_SUITES}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.ssl_excluded_cipher_suites=${CONTAINER_SSL_EXCLUDED_CIPHER_SUITES}" +fi + +if [ "x${CONTAINER_SSL_EXCLUDED_PROTOCOLS}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.ssl_excluded_protocols=${CONTAINER_SSL_EXCLUDED_PROTOCOLS}" +fi + +if [ "x${CONTAINER_ACCESS_LOG_DIR}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.access_log_dir=${CONTAINER_ACCESS_LOG_DIR}" +fi + +if [ "x${CONTAINER_ACCESS_LOG_NAME}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.access_log_name=${CONTAINER_ACCESS_LOG_NAME}" +fi + +if [ "x${CONTAINER_ACCESS_LOG_RETAIN_DAYS}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.access_log_retain_days=${CONTAINER_ACCESS_LOG_RETAIN_DAYS}" +fi + +if [ "x${CONTAINER_LISTEN_HOST}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.listen_host=${CONTAINER_LISTEN_HOST}" +fi + +if [ "x${CONTAINER_AUTHZ_SERVICE_FNAME}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.authz_service_fname=${CONTAINER_AUTHZ_SERVICE_FNAME}" +fi + +if [ "x${CONTAINER_SOLUTION_TEMPLATES_FNAME}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.solution_templates_fname=${CONTAINER_SOLUTION_TEMPLATES_FNAME}" +fi + +if [ "x${CONTAINER_DOMAIN_NAME_MAX_LEN}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.domain_name_max_len=${CONTAINER_DOMAIN_NAME_MAX_LEN}" +fi + +if [ "x${CONTAINER_AUTHORITY_CLASSES}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zms.authority_classes=${CONTAINER_AUTHORITY_CLASSES}" +fi + +if [ "x${CONTAINER_AUTH_PAM_SERVICE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.auth.user.pam_service_name=${CONTAINER_AUTH_PAM_SERVICE}" +fi + +echo "Executing: java -classpath ${CONTAINER_CLASSPATH} ${JAVA_OPTS} ${CONTAINER_BOOTSTRAP_CLASS}" + +java -classpath ${CONTAINER_CLASSPATH} ${JAVA_OPTS} ${CONTAINER_BOOTSTRAP_CLASS} 2>&1 < /dev/null diff --git a/servers/zms/src/main/java/com/yahoo/athenz/provider/ProviderClient.java b/servers/zms/src/main/java/com/yahoo/athenz/provider/ProviderClient.java new file mode 100644 index 00000000000..15439eef36f --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/provider/ProviderClient.java @@ -0,0 +1,154 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.provider; + +import com.yahoo.rdl.*; +import javax.ws.rs.client.*; +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import javax.net.ssl.HostnameVerifier; + +public class ProviderClient { + Client client; + WebTarget base; + String credsHeader; + String credsToken; + + public ProviderClient(String url) { + client = ClientBuilder.newClient(); + base = client.target(url); + } + + public ProviderClient(String url, HostnameVerifier hostnameVerifier) { + client = ClientBuilder.newBuilder() + .hostnameVerifier(hostnameVerifier) + .build(); + base = client.target(url); + } + + public void close() { + client.close(); + } + + public ProviderClient setProperty(String name, Object value) { + client = client.property(name, value); + return this; + } + + public ProviderClient addCredentials(String header, String token) { + credsHeader = header; + credsToken = token; + return this; + } + + public Tenant putTenant(String service, String tenant, String auditRef, Tenant template) { + WebTarget target = base.path("/service/{service}/tenant/{tenant}") + .resolveTemplate("service", service) + .resolveTemplate("tenant", tenant); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(template, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Tenant.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Tenant getTenant(String service, String tenant) { + WebTarget target = base.path("/service/{service}/tenant/{tenant}") + .resolveTemplate("service", service) + .resolveTemplate("tenant", tenant); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + Response response = invocationBuilder.get(); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(Tenant.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public Tenant deleteTenant(String service, String tenant, String auditRef) { + WebTarget target = base.path("/service/{service}/tenant/{tenant}") + .resolveTemplate("service", service) + .resolveTemplate("tenant", tenant); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TenantResourceGroup putTenantResourceGroup(String service, String tenant, String resourceGroup, String auditRef, TenantResourceGroup template) { + WebTarget target = base.path("/service/{service}/tenant/{tenant}/resourceGroup/{resourceGroup}") + .resolveTemplate("service", service) + .resolveTemplate("tenant", tenant) + .resolveTemplate("resourceGroup", resourceGroup); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.put(javax.ws.rs.client.Entity.entity(template, "application/json")); + int code = response.getStatus(); + switch (code) { + case 200: + return response.readEntity(TenantResourceGroup.class); + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + + public TenantResourceGroup deleteTenantResourceGroup(String service, String tenant, String resourceGroup, String auditRef) { + WebTarget target = base.path("/service/{service}/tenant/{tenant}/resourceGroup/{resourceGroup}") + .resolveTemplate("service", service) + .resolveTemplate("tenant", tenant) + .resolveTemplate("resourceGroup", resourceGroup); + Invocation.Builder invocationBuilder = target.request("application/json"); + if (credsHeader != null) { + invocationBuilder = invocationBuilder.header(credsHeader, credsToken); + } + if (auditRef != null) { + invocationBuilder = invocationBuilder.header("Y-Audit-Ref", auditRef); + } + Response response = invocationBuilder.delete(); + int code = response.getStatus(); + switch (code) { + case 204: + return null; + default: + throw new ResourceException(code, response.readEntity(ResourceError.class)); + } + + } + +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/provider/ResourceError.java b/servers/zms/src/main/java/com/yahoo/athenz/provider/ResourceError.java new file mode 100644 index 00000000000..ee389104bb2 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/provider/ResourceError.java @@ -0,0 +1,24 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.provider; + +public class ResourceError { + + public int code; + public String message; + + public ResourceError code(int code) { + this.code = code; + return this; + } + public ResourceError message(String message) { + this.message = message; + return this; + } + + public String toString() { + return "{code: " + code + ", message: \"" + message + "\"}"; + } + +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/provider/ResourceException.java b/servers/zms/src/main/java/com/yahoo/athenz/provider/ResourceException.java new file mode 100644 index 00000000000..ded276d615a --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/provider/ResourceException.java @@ -0,0 +1,79 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.provider; + +public class ResourceException extends RuntimeException { + public final static int OK = 200; + public final static int CREATED = 201; + public final static int ACCEPTED = 202; + public final static int NO_CONTENT = 204; + public final static int MOVED_PERMANENTLY = 301; + public final static int FOUND = 302; + public final static int SEE_OTHER = 303; + public final static int NOT_MODIFIED = 304; + public final static int TEMPORARY_REDIRECT = 307; + public final static int BAD_REQUEST = 400; + public final static int UNAUTHORIZED = 401; + public final static int FORBIDDEN = 403; + public final static int NOT_FOUND = 404; + public final static int CONFLICT = 409; + public final static int GONE = 410; + public final static int PRECONDITION_FAILED = 412; + public final static int UNSUPPORTED_MEDIA_TYPE = 415; + public final static int INTERNAL_SERVER_ERROR = 500; + public final static int NOT_IMPLEMENTED = 501; + + public final static int SERVICE_UNAVAILABLE = 503; + + public static String codeToString(int code) { + switch (code) { + case OK: return "OK"; + case CREATED: return "Created"; + case ACCEPTED: return "Accepted"; + case NO_CONTENT: return "No Content"; + case MOVED_PERMANENTLY: return "Moved Permanently"; + case FOUND: return "Found"; + case SEE_OTHER: return "See Other"; + case NOT_MODIFIED: return "Not Modified"; + case TEMPORARY_REDIRECT: return "Temporary Redirect"; + case BAD_REQUEST: return "Bad Request"; + case UNAUTHORIZED: return "Unauthorized"; + case FORBIDDEN: return "Forbidden"; + case NOT_FOUND: return "Not Found"; + case CONFLICT: return "Conflict"; + case GONE: return "Gone"; + case PRECONDITION_FAILED: return "Precondition Failed"; + case UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type"; + case INTERNAL_SERVER_ERROR: return "Internal Server Error"; + case NOT_IMPLEMENTED: return "Not Implemented"; + default: return "" + code; + } + } + + int code; + Object data; + + public ResourceException(int code) { + this(code, new ResourceError().code(code).message(codeToString(code))); + } + + public ResourceException(int code, Object data) { + super("ResourceException (" + code + "): " + data); + this.code = code; + this.data = data; + } + + public int getCode() { + return code; + } + + public Object getData() { + return data; + } + + public T getData(Class cl) { + return cl.cast(data); + } + +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/DBService.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/DBService.java new file mode 100644 index 00000000000..2281c81dd46 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/DBService.java @@ -0,0 +1,2579 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.common.server.util.StringUtils; +import com.yahoo.athenz.zms.store.AthenzDomain; +import com.yahoo.athenz.zms.store.ObjectStore; +import com.yahoo.athenz.zms.store.ObjectStoreConnection; +import com.yahoo.athenz.zms.utils.ZMSUtils; +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Timestamp; +import com.yahoo.rdl.UUID; + +public class DBService { + + ObjectStore store; + String userDomain; + AuditLogger auditLogger; + Cache cacheStore; + int retrySleepTime = 250; + int defaultRetryCount = 120; + + private static final Logger LOG = LoggerFactory.getLogger(DBService.class); + + private static final String ROLE_PREFIX = "role."; + private static final String POLICY_PREFIX = "policy."; + private static final String TEMPLATE_DOMAIN_NAME = "_domain_"; + + public DBService(ObjectStore store, AuditLogger auditLogger, String userDomain) { + + this.store = store; + this.userDomain = userDomain; + this.auditLogger = auditLogger; + cacheStore = CacheBuilder.newBuilder().concurrencyLevel(25).build(); + + // retrieve the concurrent update retry count. If we're given an invalid negative + // value for count, we'll default back to our default configured value of 120 retries + // which would result up to 30 seconds sleeping 250ms each time + + defaultRetryCount = Integer.parseInt(System.getProperty(ZMSConsts.ZMS_PROP_CONFLICT_RETRY_COUNT, "120")); + if (defaultRetryCount < 0) { + defaultRetryCount = 120; + } + + retrySleepTime = Integer.parseInt(System.getProperty(ZMSConsts.ZMS_PROP_CONFLICT_RETRY_SLEEP_TIME, "250")); + if (retrySleepTime < 0) { + retrySleepTime = 250; + } + } + + private static class DataCache { + AthenzDomain athenzDomain; + long modTime; + + DataCache(AthenzDomain athenzDomain, long modTime) { + this.athenzDomain = athenzDomain; + this.modTime = modTime; + } + + AthenzDomain getAthenzDomain() { + return athenzDomain; + } + + long getModTime() { + return modTime; + } + } + + AthenzDomain getAthenzDomainFromCache(String domainName) { + + // if we have a match for a given domain name then we're going + // to check if the last modified domain timestamp matches to what's + // in the db: So if there is no match, then we'll take the hit + // of extra db read, however, in most cases the domain data is not + // changed that often so we'll satisfy the request with just + // verifying the last modification time as oppose to reading the + // full domain data from db + + DataCache data = cacheStore.getIfPresent(domainName); + if (data == null) { + return null; + } + + long modTime = 0; + try (ObjectStoreConnection con = store.getConnection(true)) { + modTime = con.getDomainModTimestamp(domainName); + } + + if (modTime == data.getModTime()) { + return data.getAthenzDomain(); + } + + cacheStore.invalidate(domainName); + return null; + } + + String getPrincipalYrn(ResourceContext ctx) { + if (ctx == null) { + return null; + } + Principal principal = ((ZMSImpl.RsrcCtxWrapper) ctx).principal(); + if (principal == null) { + return null; + } + return principal.getYRN(); + } + + void saveChanges(ObjectStoreConnection con, String domainName) { + + // we're first going to commit our changes which will + // also set the connection in auto-commit mode. we are + // going to change the domain timestamp in auto-commit + // mode so that we don't have a contention + + con.commitChanges(); + con.updateDomainModTimestamp(domainName); + cacheStore.invalidate(domainName); + } + + void auditLogRequest(ResourceContext ctx, String domainName, String auditRef, + String caller, String operation, String entityName, String auditDetails) { + + AuditLogMsgBuilder msgBldr = ZMSImpl.getAuditLogMsgBuilder(ctx, domainName, auditRef, + caller, operation); + msgBldr.when(Timestamp.fromCurrentTime()).whatEntity(entityName); + if (auditDetails != null) { + msgBldr.whatDetails(auditDetails); + } + auditLogger.log(msgBldr); + } + + Domain makeDomain(ResourceContext ctx, String domainName, String description, String org, + Boolean auditEnabled, List adminUsers, String account, int productId, + List solutionTemplates, String auditRef) { + + final String caller = "makedomain"; + + Domain domain = new Domain() + .setName(domainName) + .setAuditEnabled(auditEnabled) + .setDescription(description) + .setOrg(org) + .setId(UUID.fromCurrentTime()) + .setAccount(account) + .setYpmId(productId) + .setModified(Timestamp.fromCurrentTime()); + + // get our connection object + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + boolean objectsInserted = con.insertDomain(domain); + if (!objectsInserted) { + con.rollbackChanges(); + throw ZMSUtils.requestError("makeDomain: Cannot create domain: " + + domainName + " - already exists", caller); + } + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditDetails.append("{domain: "); + auditLogDomain(auditDetails, domain); + + // first create and process the admin role + + Role adminRole = ZMSUtils.makeAdminRole(domainName, adminUsers); + auditDetails.append(", role: "); + if (!processRole(con, null, domainName, ZMSConsts.ADMIN_ROLE_NAME, adminRole, + getPrincipalYrn(ctx), auditRef, false, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("makeDomain: Cannot process role: '" + + adminRole.getName(), caller); + } + + // now create and process the admin policy + + Policy adminPolicy = ZMSUtils.makeAdminPolicy(domainName, adminRole); + auditDetails.append(", policy: "); + if (!processPolicy(con, null, domainName, ZMSConsts.ADMIN_POLICY_NAME, adminPolicy, + false, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("makeDomain: Cannot process policy: '" + + adminPolicy.getName(), caller); + } + + // go through our list of templates and add the specified + // roles and polices to our domain + + if (solutionTemplates != null) { + for (String templateName : solutionTemplates) { + auditDetails.append(", template: "); + Template template = ZMSImpl.serverSolutionTemplates.get(templateName); + if (!applySolutionTemplate(con, domainName, templateName, template, true, + getPrincipalYrn(ctx), auditRef, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("makeDomain: Cannot apply templates: '" + + domain, caller); + } + } + } + auditDetails.append("}"); + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log entry + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_POST, + domainName, auditDetails.toString()); + + return domain; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + + return null; + } + + boolean processPolicy(ObjectStoreConnection con, Policy originalPolicy, String domainName, + String policyName, Policy policy, boolean ignoreDeletes, StringBuilder auditDetails) { + + // check to see if we need to insert the policy or update it + + boolean requestSuccess = false; + if (originalPolicy == null) { + requestSuccess = con.insertPolicy(domainName, policy); + } else { + requestSuccess = con.updatePolicy(domainName, policy); + } + + // if we didn't update any policies then we need to return failure + + if (!requestSuccess) { + return false; + } + + // open our audit record + + auditDetails.append("{name: \"").append(policyName).append('\"'); + + // now we need process our policy assertions depending this is + // a new insert operation or an update + + List newAssertions = policy.getAssertions(); + if (originalPolicy == null) { + + // we're just going to process our new assertions + + if (newAssertions != null) { + for (Assertion assertion : newAssertions) { + if (!con.insertAssertion(domainName, policyName, assertion)) { + return false; + } + } + auditLogAssertions(auditDetails, "added-assertions", newAssertions); + } + + } else { + + // first we need to retrieve the current set of assertions + + List curAssertions = originalPolicy.getAssertions(); + if (curAssertions == null) { + curAssertions = new ArrayList<>(); + } + List addAssertions = new ArrayList<>(); + List delAssertions = new ArrayList<>(); + policyAssertionChanges(newAssertions, curAssertions, addAssertions, delAssertions); + + if (!ignoreDeletes) { + for (Assertion assertion : delAssertions) { + if (!con.deleteAssertion(domainName, policyName, assertion.getId())) { + return false; + } + } + auditLogAssertions(auditDetails, "deleted-assertions", delAssertions); + } + + for (Assertion assertion : addAssertions) { + if (!con.insertAssertion(domainName, policyName, assertion)) { + return false; + } + } + auditLogAssertions(auditDetails, "added-assertions", addAssertions); + } + + auditDetails.append('}'); + return true; + } + + boolean removeMatchedAssertion(Assertion assertion, List assertions, List matchedAssertions) { + + AssertionEffect effect = AssertionEffect.ALLOW; + if (assertion.getEffect() != null) { + effect = assertion.getEffect(); + } + + Iterator itr = assertions.iterator(); + while (itr.hasNext()) { + + Assertion checkAssertion = (Assertion) itr.next(); + + if (!assertion.getAction().equals(checkAssertion.getAction())) { + continue; + } + if (!assertion.getResource().equals(checkAssertion.getResource())) { + continue; + } + if (!assertion.getRole().equals(checkAssertion.getRole())) { + continue; + } + + AssertionEffect checkEffect = AssertionEffect.ALLOW; + if (checkAssertion.getEffect() != null) { + checkEffect = checkAssertion.getEffect(); + } + + if (effect != checkEffect) { + continue; + } + + itr.remove(); + matchedAssertions.add(checkAssertion); + return true; + } + + return false; + } + + void policyAssertionChanges(List newAssertions, List curAssertions, + List addAssertions, List delAssertions) { + + // let's iterate through the new list and the ones that are + // not in the current list should be added to the add list + + List matchedAssertions = new ArrayList(); + if (newAssertions != null) { + for (Assertion assertion : newAssertions) { + if (!removeMatchedAssertion(assertion, curAssertions, matchedAssertions)) { + addAssertions.add(assertion); + } + } + } + + // now our current list has been updated as well and + // all the assertions that were present moved to the + // matched assertion list so whatever left in the + // current list must be deleted + + delAssertions.addAll(curAssertions); + + // now let's go back and re-add the matched assertions + // back to our list so we can get the right audit data + + curAssertions.addAll(matchedAssertions); + } + + boolean processRole(ObjectStoreConnection con, Role originalRole, String domainName, String roleName, + Role role, String admin, String auditRef, boolean ignoreDeletes, StringBuilder auditDetails) { + + // check to see if we need to insert the role or update it + + boolean requestSuccess = false; + if (originalRole == null) { + requestSuccess = con.insertRole(domainName, role); + } else { + requestSuccess = con.updateRole(domainName, role); + } + + // if we didn't update any roles then we need to return failure + + if (!requestSuccess) { + return false; + } + + // open our audit record and log our trust field if one is available + + auditDetails.append("{name: \"").append(roleName) + .append("\", trust: \"").append(role.getTrust()).append('\"'); + + // now we need process our role members depending this is + // a new insert operation or an update + + List members = role.getMembers(); + if (originalRole == null) { + + // we are just going to process all members as new inserts + + if (members != null) { + + for (String member : members) { + if (!con.insertRoleMember(domainName, roleName, member, admin, auditRef)) { + return false; + } + } + auditLogStrings(auditDetails, "added-members", members); + } + + } else { + + // first we need to retrieve the current set of members + + List originalMembers = originalRole.getMembers(); + if (originalMembers == null) { + originalMembers = new ArrayList<>(); + } + Set curMembers = new HashSet<>(originalMembers); + Set delMembers = new HashSet<>(curMembers); + if (members == null) { + members = new ArrayList<>(); + } + Set newMembers = new HashSet<>(members); + newMembers.removeAll(curMembers); + delMembers.removeAll(members); + + if (!ignoreDeletes) { + for (String member : delMembers) { + if (!con.deleteRoleMember(domainName, roleName, member, admin, auditRef)) { + return false; + } + } + auditLogStrings(auditDetails, "deleted-members", delMembers); + } + + for (String member : newMembers) { + if (!con.insertRoleMember(domainName, roleName, member, admin, auditRef)) { + return false; + } + } + auditLogStrings(auditDetails, "added-members", newMembers); + + } + + auditDetails.append('}'); + return true; + } + + boolean processServiceIdentity(ObjectStoreConnection con, ServiceIdentity originalService, String domainName, + String serviceName, ServiceIdentity service, StringBuilder auditDetails) { + + boolean requestSuccess = false; + if (originalService == null) { + requestSuccess = con.insertServiceIdentity(domainName, service); + } else { + requestSuccess = con.updateServiceIdentity(domainName, service); + } + + // if we didn't update any services then we need to return failure + + if (!requestSuccess) { + return false; + } + + // open our audit record and log our service details + + auditDetails.append("{name: \"").append(serviceName).append('\"') + .append(", executable: \"").append(service.getExecutable()).append('\"') + .append(", user: \"").append(service.getUser()).append('\"') + .append(", group: \"").append(service.getGroup()).append('\"') + .append(", providerEndpoint: \"").append(service.getProviderEndpoint()).append('\"'); + + // now we need process our public keys depending this is + // a new insert operation or an update + + List publicKeys = service.getPublicKeys(); + if (originalService == null) { + + // we are just going to process all public keys as new inserts + + if (publicKeys != null) { + + for (PublicKeyEntry publicKey : publicKeys) { + if (!con.insertPublicKeyEntry(domainName, serviceName, publicKey)) { + return false; + } + } + auditLogPublicKeyEntries(auditDetails, "added-publickeys", publicKeys); + } + + } else { + + // first we need to retrieve the current set of public keys + + List curPublicKeys = originalService.getPublicKeys(); + Map curPublicKeysMap = new HashMap<>(); + if (curPublicKeys != null) { + for (PublicKeyEntry publicKey : curPublicKeys) { + curPublicKeysMap.put(publicKey.getId(), publicKey); + } + } + Map publicKeysMap = new HashMap<>(); + if (publicKeys != null) { + for (PublicKeyEntry publicKey : publicKeys) { + publicKeysMap.put(publicKey.getId(), publicKey); + } + } + Set curPublicKeysSet = new HashSet<>(curPublicKeysMap.keySet()); + Set delPublicKeysSet = new HashSet<>(curPublicKeysSet); + Set newPublicKeysSet = new HashSet<>(publicKeysMap.keySet()); + newPublicKeysSet.removeAll(curPublicKeysSet); + delPublicKeysSet.removeAll(new HashSet<>(publicKeysMap.keySet())); + + for (String publicKey : delPublicKeysSet) { + if (!con.deletePublicKeyEntry(domainName, serviceName, publicKey)) { + return false; + } + } + auditLogPublicKeyEntries(auditDetails, "deleted-publickeys", delPublicKeysSet); + + for (String publicKey : newPublicKeysSet) { + if (!con.insertPublicKeyEntry(domainName, serviceName, publicKeysMap.get(publicKey))) { + return false; + } + } + auditLogPublicKeyEntries(auditDetails, "added-publickeys", newPublicKeysSet, publicKeysMap); + } + + // now we need to process the hosts defined for this service + + Set curHosts = null; + if (originalService != null && originalService.getHosts() != null) { + curHosts = new HashSet<>(originalService.getHosts()); + } else { + curHosts = new HashSet<>(); + } + + Set newHosts = null; + if (service.getHosts() != null) { + newHosts = new HashSet<>(service.getHosts()); + } else { + newHosts = new HashSet<>(); + } + + Set delHosts = new HashSet<>(curHosts); + delHosts.removeAll(newHosts); + newHosts.removeAll(curHosts); + + for (String host : delHosts) { + if (!con.deleteServiceHost(domainName, serviceName, host)) { + return false; + } + } + auditLogStrings(auditDetails, "deleted-hosts", delHosts); + + for (String host : newHosts) { + if (!con.insertServiceHost(domainName, serviceName, host)) { + return false; + } + } + auditLogStrings(auditDetails, "added-hosts", newHosts); + + auditDetails.append('}'); + return true; + } + + boolean shouldRetryOperation(ResourceException ex, int retryCount) { + + // before doing anything else let's check to see if + // we still have the option to retry the operation + + if (retryCount <= 1) { + return false; + } + + // if we got a conflict result it means we either had + // no connection or deadlock was detected and as such + // the changes were aborted + + boolean retry = false; + switch (ex.getCode()) { + + case ResourceException.CONFLICT: + + retry = true; + break; + + case ResourceException.GONE: + + // this error indicates that the server is reporting is in + // read-only mode which indicates a fail-over has taken place + // and we need to clear all connections and start new ones + + store.clearConnections(); + retry = true; + break; + } + + // if we're asked to retry then we're going to + // wait for a short period of time to allow the other + // connection to finish its work + + if (retry) { + + if (LOG.isDebugEnabled()) { + LOG.debug(": possible deadlock, retries available: " + retryCount); + } + + try { + Thread.sleep(retrySleepTime); + } catch (InterruptedException exc) { + } + } + + // return our response + + return retry; + } + + void executePutPolicy(ResourceContext ctx, String domainName, String policyName, Policy policy, + String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // retrieve our original policy + + Policy originalPolicy = getPolicy(con, domainName, policyName); + + // now process the request + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + if (!processPolicy(con, originalPolicy, domainName, policyName, policy, false, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("unable to put policy: " + policy.getName(), caller); + } + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_PUT, + policyName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executePutRole(ResourceContext ctx, String domainName, String roleName, Role role, + String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // retrieve our original role + + Role originalRole = getRole(con, domainName, roleName, false, false); + + // now process the request + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + if (!processRole(con, originalRole, domainName, roleName, role, + getPrincipalYrn(ctx), auditRef, false, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("unable to put role: " + role.getName(), caller); + } + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_PUT, + roleName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executePutServiceIdentity(ResourceContext ctx, String domainName, String serviceName, + ServiceIdentity service, String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // retrieve our original service identity object + + ServiceIdentity originalService = getServiceIdentity(con, domainName, serviceName); + + // now process the request + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + if (!processServiceIdentity(con, originalService, domainName, serviceName, service, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("unable to put service: " + service.getName(), caller); + } + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_PUT, + serviceName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executePutPublicKeyEntry(ResourceContext ctx, String domainName, String serviceName, + PublicKeyEntry keyEntry, String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // check to see if this key already exists or not + + PublicKeyEntry originalKeyEntry = con.getPublicKeyEntry(domainName, serviceName, keyEntry.getId()); + + // now process the request + + boolean requestSuccess = false; + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + + if (originalKeyEntry == null) { + requestSuccess = con.insertPublicKeyEntry(domainName, serviceName, keyEntry); + auditDetails.append("{added-publicKeys: ["); + } else { + requestSuccess = con.updatePublicKeyEntry(domainName, serviceName, keyEntry); + auditDetails.append("{updated-publicKeys: ["); + } + + if (!requestSuccess) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("unable to put public key: " + keyEntry.getId() + + " in service " + ZMSUtils.serviceResourceName(domainName, serviceName), caller); + } + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + auditLogPublicKeyEntry(auditDetails, keyEntry, true); + auditDetails.append("]}"); + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_PUT, + serviceName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executeDeletePublicKeyEntry(ResourceContext ctx, String domainName, String serviceName, + String keyId, String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // verify that we don't want to delete the last public key + + List publicKeys = con.listPublicKeys(domainName, serviceName); + if (publicKeys.size() == 1 && publicKeys.get(0).getId().equals(keyId)) { + throw ZMSUtils.requestError("cannot remove last public key from service", caller); + } + + // now process the request + + if (!con.deletePublicKeyEntry(domainName, serviceName, keyId)) { + con.rollbackChanges(); + throw ZMSUtils.notFoundError("unable to delete public key: " + keyId + + " in service " + ZMSUtils.serviceResourceName(domainName, serviceName), caller); + } + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditDetails.append("{deleted-publicKeys: [{id: \"").append(keyId).append("\"}]}"); + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_DELETE, + serviceName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + boolean isTrustRole(Role role) { + + if (role == null) { + return false; + } + + if (role.getTrust() == null || role.getTrust().isEmpty()) { + return false; + } + + return true; + } + + void executePutMembership(ResourceContext ctx, String domainName, String roleName, + String normalizedMember, String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(true)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // before inserting a member we need to verify that + // this is a group role and not a delegated one. + + if (isTrustRole(con.getRole(domainName, roleName))) { + con.rollbackChanges(); + throw ZMSUtils.requestError(caller + ": " + roleName + + "is a delegated role", caller); + } + + // process our insert role member support. since this is a "single" + // operation, we are not using any transactions. + + if (!con.insertRoleMember(domainName, roleName, normalizedMember, + getPrincipalYrn(ctx), auditRef)) { + con.rollbackChanges(); + throw ZMSUtils.requestError(caller + ": unable to insert role member: " + + normalizedMember + " to role: " + roleName, caller); + } + + // update our role and domain time-stamps, and invalidate local cache entry + + con.updateRoleModTimestamp(domainName, roleName); + con.updateDomainModTimestamp(domainName); + cacheStore.invalidate(domainName); + + // audit log the request + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditDetails.append("{member: \"").append(normalizedMember).append("\"}"); + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_PUT, + roleName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + + // otherwise check if we need to retry or return failure + + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executePutEntity(ResourceContext ctx, String domainName, String entityName, Entity entity, + String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // check to see if this key already exists or not + + Entity originalEntity = con.getEntity(domainName, entityName); + + // now process the request + + boolean requestSuccess = false; + if (originalEntity == null) { + requestSuccess = con.insertEntity(domainName, entity); + } else { + requestSuccess = con.updateEntity(domainName, entity); + } + + if (!requestSuccess) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("unable to put entity: " + entity.getName(), caller); + } + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_PUT, + entity.getName(), JSON.string(entity.getValue())); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executeDeleteMembership(ResourceContext ctx, String domainName, String roleName, + String normalizedMember, String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(true)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // if this is the admin role then we need to make sure + // the admin is not himself who happens to be the last + // member in the role + + if (ZMSConsts.ADMIN_ROLE_NAME.equals(roleName)) { + List members = con.listRoleMembers(domainName, roleName); + if (members.size() == 1 && members.get(0).equals(normalizedMember)) { + throw ZMSUtils.forbiddenError(caller + ": Cannot delete last member of 'admin' role", caller); + } + } + + // process our delete role member operation + + if (!con.deleteRoleMember(domainName, roleName, normalizedMember, + getPrincipalYrn(ctx), auditRef)) { + con.rollbackChanges(); + throw ZMSUtils.notFoundError(caller + ": unable to delete role member: " + + normalizedMember + " from role: " + roleName, caller); + } + + // update our role and domain time-stamps, and invalidate local cache entry + + con.updateRoleModTimestamp(domainName, roleName); + con.updateDomainModTimestamp(domainName); + cacheStore.invalidate(domainName); + + // audit log the request + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditDetails.append("{member: \"").append(normalizedMember).append("\"}"); + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_DELETE, + roleName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executeDeleteServiceIdentity(ResourceContext ctx, String domainName, String serviceName, + String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // process our delete service request + + if (!con.deleteServiceIdentity(domainName, serviceName)) { + con.rollbackChanges(); + throw ZMSUtils.notFoundError(caller + ": unable to delete service: " + serviceName, caller); + } + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_DELETE, + serviceName, null); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executeDeleteEntity(ResourceContext ctx, String domainName, String entityName, + String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // process our delete role request + + if (!con.deleteEntity(domainName, entityName)) { + con.rollbackChanges(); + throw ZMSUtils.notFoundError(caller + ": unable to delete entity: " + entityName, caller); + } + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_DELETE, + entityName, null); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executeDeleteRole(ResourceContext ctx, String domainName, String roleName, + String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // process our delete role request + + if (!con.deleteRole(domainName, roleName)) { + con.rollbackChanges(); + throw ZMSUtils.notFoundError(caller + ": unable to delete role: " + roleName, caller); + } + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_DELETE, + roleName, null); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executeDeletePolicy(ResourceContext ctx, String domainName, String policyName, + String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // process our delete policy request + + if (!con.deletePolicy(domainName, policyName)) { + con.rollbackChanges(); + throw ZMSUtils.notFoundError(caller + ": unable to delete policy: " + policyName, caller); + } + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_DELETE, + policyName, null); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + /** + * If the domain has audit enabled, and user did not provide the auditRef, + * an exception will be thrown. This is the first check before any write + * operation is carried out so we don't really have anything to roll-back + **/ + Domain checkDomainAuditEnabled(ObjectStoreConnection con, String domainName, String auditRef, String caller) { + + Domain domain = con.getDomain(domainName); + if (domain == null) { + con.rollbackChanges(); + throw ZMSUtils.notFoundError(caller + ": Unknown domain: " + domainName, caller); + } + + if (domain.getAuditEnabled() && (auditRef == null || auditRef.length() == 0)) { + con.rollbackChanges(); + throw ZMSUtils.requestError(caller + ": Audit reference required for domain: " + domainName, caller); + } + + return domain; + } + + Domain executeDeleteDomain(ResourceContext ctx, String domainName, String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + Domain domain = checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // now process the request + + con.deleteDomain(domainName); + con.commitChanges(); + cacheStore.invalidate(domainName); + + // audit log the request + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_DELETE, + domainName, null); + + return domain; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + + return null; + } + + ServiceIdentity getServiceIdentity(String domainName, String serviceName) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return getServiceIdentity(con, domainName, serviceName); + } + } + + DomainTemplateList listDomainTemplates(String domainName) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + DomainTemplateList domainTemplateList = new DomainTemplateList(); + domainTemplateList.setTemplateNames(con.listDomainTemplates(domainName)); + return domainTemplateList; + } + } + + ServiceIdentity getServiceIdentity(ObjectStoreConnection con, String domainName, String serviceName) { + + ServiceIdentity service = con.getServiceIdentity(domainName, serviceName); + if (service != null) { + service.setPublicKeys(con.listPublicKeys(domainName, serviceName)); + List hosts = con.listServiceHosts(domainName, serviceName); + if (hosts != null && !hosts.isEmpty()) { + service.setHosts(hosts); + } + } + return service; + } + + PublicKeyEntry getServicePublicKeyEntry(String domainName, String serviceName, String keyId) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.getPublicKeyEntry(domainName, serviceName, keyId); + } + } + + public ResourceAccessList getResourceAccessList(String principal, String action) { + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.listResourceAccess(principal, action, userDomain); + } + } + + Domain getDomain(String domainName) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.getDomain(domainName); + } + } + + List listDomains(String prefix, long modifiedSince) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.listDomains(prefix, modifiedSince); + } + } + + DomainList lookupDomainById(String account, int productId) { + + DomainList domList = new DomainList(); + try (ObjectStoreConnection con = store.getConnection(true)) { + String domain = con.lookupDomainById(account, productId); + if (domain != null) { + List list = Arrays.asList(domain); + domList.setNames(list); + } + } + return domList; + } + + DomainList lookupDomainByAccount(String account) { + return lookupDomainById(account, 0); + } + + DomainList lookupDomainByProductId(Integer productId) { + return lookupDomainById(null, productId); + } + + DomainList lookupDomainByRole(String roleMember, String roleName) { + + DomainList domList = new DomainList(); + try (ObjectStoreConnection con = store.getConnection(true)) { + List domains = con.lookupDomainByRole(roleMember, roleName); + if (domains != null) { + domList.setNames(domains); + } + } + return domList; + } + + List listRoles(String domainName) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.listRoles(domainName); + } + } + + Membership getMembership(String domainName, String roleName, String principal) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.getRoleMember(domainName, roleName, principal); + } + } + + Role getRole(String domainName, String roleName, Boolean auditLog, Boolean expand) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return getRole(con, domainName, roleName, auditLog, expand); + } + } + + Role getRole(ObjectStoreConnection con, String domainName, String roleName, + Boolean auditLog, Boolean expand) { + + Role role = con.getRole(domainName, roleName); + if (role != null) { + + if (role.getTrust() == null) { + + // if we have no trust field specified then we need to + // retrieve our standard group role members + + role.setMembers(con.listRoleMembers(domainName, roleName)); + if (auditLog != null && auditLog.booleanValue()) { + role.setAuditLog(con.listRoleAuditLogs(domainName, roleName)); + } + + } else if (expand != null && expand.booleanValue()) { + + // otherwise, if asked, let's expand the delegated + // membership and return the list of members + + role.setMembers(getDelegatedRoleMembers(domainName, role.getTrust(), roleName)); + } + } + return role; + } + + List getDelegatedRoleMembers(String domainName, String trustDomain, String roleName) { + + // verify that the domain and trust domain are not the same + + if (domainName.equals(trustDomain)) { + return null; + } + + // retrieve our trust domain + + AthenzDomain domain = null; + try { + domain = getAthenzDomain(trustDomain); + } catch (ResourceException ex) { + } + + if (domain == null) { + return null; + } + + // we need to use a set since we might be matching + // multiple assertions and we want to automatically + // skip any duplicate members + + Set roleMembers = new HashSet<>(); + + // generate our full role name + + String fullRoleName = ZMSUtils.roleResourceName(domainName, roleName); + + // iterate through all policies to see which one has the + // assume_role assertion for the given role + + for (Policy policy : domain.getPolicies()) { + + List assertions = policy.getAssertions(); + if (assertions == null) { + continue; + } + + for (Assertion assertion : assertions) { + + if (!ZMSUtils.assumeRoleResourceMatch(fullRoleName, assertion)) { + continue; + } + + String rolePattern = StringUtils.patternFromGlob(assertion.getRole()); + for (Role role : domain.getRoles()) { + + // make sure we have members before trying to match the name + + List members = role.getMembers(); + if (members == null || members.isEmpty()) { + continue; + } + + if (!role.getName().matches(rolePattern)) { + continue; + } + + roleMembers.addAll(role.getMembers()); + } + } + } + + return new ArrayList(roleMembers); + } + + Policy getPolicy(String domainName, String policyName) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return getPolicy(con, domainName, policyName); + } + } + + Assertion getAssertion(String domainName, String policyName, Long assertionId) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.getAssertion(domainName, policyName, assertionId); + } + } + + public void executePutAssertion(ResourceContext ctx, String domainName, String policyName, + Assertion assertion, String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(true)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // process our insert assertion. since this is a "single" + // operation, we are not using any transactions. + + if (!con.insertAssertion(domainName, policyName, assertion)) { + throw ZMSUtils.requestError(caller + ": unable to insert assertion: " + + " to policy: " + policyName, caller); + } + + // update our policy and domain time-stamps, and invalidate local cache entry + + con.updatePolicyModTimestamp(domainName, policyName); + con.updateDomainModTimestamp(domainName); + cacheStore.invalidate(domainName); + + // audit log the request + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditLogAssertion(auditDetails, assertion, true); + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_PUT, + policyName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + + // otherwise check if we need to retry or return failure + + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + public void executeDeleteAssertion(ResourceContext ctx, String domainName, String policyName, + Long assertionId, String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(true)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // process our delete assertion. since this is a "single" + // operation, we are not using any transactions. + + if (!con.deleteAssertion(domainName, policyName, assertionId)) { + throw ZMSUtils.requestError(caller + ": unable to delete assertion: " + + assertionId + " from policy: " + policyName, caller); + } + + // update our policy and domain time-stamps, and invalidate local cache entry + + con.updatePolicyModTimestamp(domainName, policyName); + con.updateDomainModTimestamp(domainName); + cacheStore.invalidate(domainName); + + // audit log the request + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditDetails.append("assertionId=(").append(assertionId).append(')'); + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_DELETE, + policyName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + + // otherwise check if we need to retry or return failure + + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + List listEntities(String domainName) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.listEntities(domainName); + } + } + + Entity getEntity(String domainName, String entityName) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.getEntity(domainName, entityName); + } + } + + Policy getPolicy(ObjectStoreConnection con, String domainName, String policyName) { + + Policy policy = con.getPolicy(domainName, policyName); + if (policy != null) { + policy.setAssertions(con.listAssertions(domainName, policyName)); + } + return policy; + } + + List listPolicies(String domainName) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.listPolicies(domainName, null); + } + } + + List listServiceIdentities(String domainName) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.listServiceIdentities(domainName); + } + } + + void executePutDomainMeta(ResourceContext ctx, String domainName, DomainMeta meta, + String auditRef, String caller) { + + int retryCount = defaultRetryCount; + Domain domain = null; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + domain = checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // now process the request + + Domain updatedDomain = new Domain() + .setName(domain.getName()) + .setEnabled(domain.getEnabled()) + .setId(domain.getId()) + .setAuditEnabled(meta.getAuditEnabled()) + .setDescription(meta.getDescription()) + .setOrg(meta.getOrg()); + + // we'll only update aws/product ids if the meta + // object does not contain nulls + + if (meta.getAccount() == null && meta.getYpmId() == null) { + updatedDomain.setAccount(domain.getAccount()); + updatedDomain.setYpmId(domain.getYpmId()); + } else { + updatedDomain.setYpmId(meta.getYpmId()); + updatedDomain.setAccount(meta.getAccount()); + } + + con.updateDomain(updatedDomain); + con.commitChanges(); + cacheStore.invalidate(domainName); + + // audit log the request + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditLogDomain(auditDetails, updatedDomain); + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_PUT, + domainName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executePutDomainTemplate(ResourceContext ctx, String domainName, List templateNames, + String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // go through our list of templates and add the specified + // roles and polices to our domain + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditDetails.append("{add-templates: "); + boolean firstEntry = true; + + for (String templateName : templateNames) { + Template template = ZMSImpl.serverSolutionTemplates.get(templateName); + firstEntry = auditLogSeparator(auditDetails, firstEntry); + if (!applySolutionTemplate(con, domainName, templateName, template, true, + getPrincipalYrn(ctx), auditRef, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("unable to put domain templates: " + domainName, caller); + } + } + auditDetails.append("}"); + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_PUT, + domainName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executeDeleteDomainTemplate(ResourceContext ctx, String domainName, String templateName, + String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, domainName, auditRef, caller); + + // go through our list of templates and add the specified + // roles and polices to our domain + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditDetails.append("{templates: "); + + Template template = ZMSImpl.serverSolutionTemplates.get(templateName); + if (!applySolutionTemplate(con, domainName, templateName, template, false, + getPrincipalYrn(ctx), auditRef, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("unable to delete domain template: " + domainName, caller); + } + + auditDetails.append("}"); + + // update our domain time-stamp and save changes + + saveChanges(con, domainName); + + // audit log the request + + auditLogRequest(ctx, domainName, auditRef, caller, ZMSConsts.HTTP_DELETE, + domainName, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + boolean applySolutionTemplate(ObjectStoreConnection con, String domainName, String templateName, + Template template, boolean addTemplate, String admin, String auditRef, StringBuilder auditDetails) { + + auditDetails.append("{name: \"").append(templateName).append('\"'); + + // we have already verified that our template is valid but + // we'll just double check to make sure it's not null + + if (template == null) { + auditDetails.append("}"); + return true; + } + + boolean firstEntry = true; + + // iterate through roles in the list. + // When adding a template, if the role does not exist in our domain + // then insert it otherwise only apply the changes to the member list. + // otherwise for delete request, we just the delete role + + List templateRoles = template.getRoles(); + if (templateRoles != null) { + for (Role role : templateRoles) { + String roleName = ZMSUtils.removeDomainPrefix(role.getName(), + TEMPLATE_DOMAIN_NAME, ROLE_PREFIX); + + if (!addTemplate) { + con.deleteRole(domainName, roleName); + firstEntry = auditLogSeparator(auditDetails, firstEntry); + auditDetails.append(" delete-role: \"").append(roleName).append('\"'); + continue; + } + + // retrieve our original role + + Role originalRole = getRole(con, domainName, roleName, false, false); + + // now process the request + + Role templateRole = updateTemplateRole(role, domainName, roleName); + firstEntry = auditLogSeparator(auditDetails, firstEntry); + auditDetails.append(" add-role: "); + if (!processRole(con, originalRole, domainName, roleName, templateRole, + admin, auditRef, true, auditDetails)) { + return false; + } + } + } + + // iterate through policies in the list. + // When adding a template, if the policy does not exist in our domain + // then insert it otherwise only apply the changes to the assertions + // otherwise for delete requests, we just delete the policy + + List templatePolicies = template.getPolicies(); + if (templatePolicies != null) { + for (Policy policy : templatePolicies) { + String policyName = ZMSUtils.removeDomainPrefix(policy.getName(), + TEMPLATE_DOMAIN_NAME, POLICY_PREFIX); + + if (!addTemplate) { + con.deletePolicy(domainName, policyName); + firstEntry = auditLogSeparator(auditDetails, firstEntry); + auditDetails.append(" delete-policy: \"").append(policyName).append('\"'); + continue; + } + + // retrieve our original policy + + Policy originalPolicy = getPolicy(con, domainName, policyName); + + // now process the request + + Policy templatePolicy = updateTemplatePolicy(policy, domainName, policyName); + firstEntry = auditLogSeparator(auditDetails, firstEntry); + auditDetails.append(" add-policy: "); + if (!processPolicy(con, originalPolicy, domainName, policyName, templatePolicy, + true, auditDetails)) { + return false; + } + } + } + + // if deleting a template, delete it from the current list + // if adding a template, only add if it is not in our current list + + if (!addTemplate) { + con.deleteDomainTemplate(domainName, templateName, null); + } else { + // check to see if the template is already listed for the domain + + List currentTemplateList = con.listDomainTemplates(domainName); + if (!currentTemplateList.contains(templateName)) { + con.insertDomainTemplate(domainName, templateName, null); + } + } + + auditDetails.append("}"); + return true; + } + + Role updateTemplateRole(Role role, String domainName, String roleName) { + + Role templateRole = new Role() + .setName(ZMSUtils.roleResourceName(domainName, roleName)) + .setTrust(role.getTrust()); + List roleMembers = role.getMembers(); + List newMembers = new ArrayList<>(); + if (roleMembers != null && !roleMembers.isEmpty()) { + newMembers.addAll(roleMembers); + } + templateRole.setMembers(newMembers); + return templateRole; + } + + Policy updateTemplatePolicy(Policy policy, String domainName, String policyName) { + + Policy templatePolicy = new Policy().setName(ZMSUtils.policyResourceName(domainName, policyName)); + List assertions = policy.getAssertions(); + List newAssertions = new ArrayList<>(); + if (assertions != null && !assertions.isEmpty()) { + for (Assertion assertion : assertions) { + Assertion newAssertion = new Assertion(); + newAssertion.setAction(assertion.getAction()); + newAssertion.setEffect(assertion.getEffect()); + newAssertion.setResource(assertion.getResource().replace(TEMPLATE_DOMAIN_NAME, domainName)); + newAssertion.setRole(assertion.getRole().replace(TEMPLATE_DOMAIN_NAME, domainName)); + newAssertions.add(newAssertion); + } + } + templatePolicy.setAssertions(newAssertions); + return templatePolicy; + } + + void setupTenantAdminPolicy(ResourceContext ctx, String tenantDomain, String provSvcDomain, + String provSvcName, String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, tenantDomain, auditRef, caller); + + String domainAdminRole = ZMSUtils.roleResourceName(tenantDomain, ZMSConsts.ADMIN_ROLE_NAME); + String serviceRoleYRN = ZMSUtils.getTrustedResourceGroupRolePrefix(provSvcDomain, provSvcName, + tenantDomain, null) + ZMSConsts.ADMIN_ROLE_NAME; + + // our tenant admin role/policy name + + StringBuilder tenancyResourceBuilder = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + tenancyResourceBuilder.append("tenancy.").append(provSvcDomain).append('.').append(provSvcName); + String tenancyResource = tenancyResourceBuilder.toString(); + + String adminName = tenancyResource + ".admin"; + String tenantAdminRole = ZMSUtils.roleResourceName(tenantDomain, adminName); + + // tenant admin role - if it already exists then we skip it + // by default it has no members. + + if (con.getRole(tenantDomain, adminName) == null) { + con.insertRole(tenantDomain, new Role().setName(tenantAdminRole)); + } + + // tenant admin policy - check to see if this already exists. If it does + // then we don't have anything to do + + if (con.getPolicy(tenantDomain, adminName) == null) { + + Policy adminPolicy = new Policy().setName(ZMSUtils.policyResourceName(tenantDomain, adminName)); + con.insertPolicy(tenantDomain, adminPolicy); + + // we are going to create 2 assertions - one for the domain admin role + // and another for the tenant admin role + + Assertion assertion = new Assertion().setRole(domainAdminRole) + .setResource(serviceRoleYRN).setAction(ZMSConsts.ACTION_ASSUME_ROLE) + .setEffect(AssertionEffect.ALLOW); + con.insertAssertion(tenantDomain, adminName, assertion); + + assertion = new Assertion().setRole(tenantAdminRole) + .setResource(serviceRoleYRN).setAction(ZMSConsts.ACTION_ASSUME_ROLE) + .setEffect(AssertionEffect.ALLOW); + con.insertAssertion(tenantDomain, adminName, assertion); + + // the tenant admin role must have the capability to provision + // new resource groups in the domain which requires update + // action capability on resource tenancy.. + + String tenantResourceYRN = tenantDomain + ":" + tenancyResource; + assertion = new Assertion().setRole(tenantAdminRole) + .setResource(tenantResourceYRN).setAction(ZMSConsts.ACTION_UPDATE) + .setEffect(AssertionEffect.ALLOW); + con.insertAssertion(tenantDomain, adminName, assertion); + } + + // update our domain time-stamp and save changes + + saveChanges(con, tenantDomain); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executePutTenantRoles(ResourceContext ctx, String provSvcDomain, String provSvcName, String tenantDomain, + String resourceGroup, List roles, String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, provSvcDomain, auditRef, caller); + + String trustedRolePrefix = ZMSUtils.getTrustedResourceGroupRolePrefix(provSvcDomain, + provSvcName, tenantDomain, resourceGroup); + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditDetails.append("{put-tenant-roles: ["); + boolean firstEntry = true; + + for (TenantRoleAction ra : roles) { + + String tenantRole = ra.getRole(); + String tenantAction = ra.getAction(); + String trustedRole = trustedRolePrefix + tenantRole; + String trustedName = trustedRole.substring((provSvcDomain + ":role.").length()); + + Role role = new Role().setName(trustedRole).setTrust(tenantDomain); + + if (LOG.isInfoEnabled()) { + LOG.info(caller + ": add trusted Role to domain " + provSvcDomain + + ": " + trustedRole + " -> " + role); + } + + // retrieve our original role in case one exists + + Role originalRole = getRole(con, provSvcDomain, trustedRole, false, false); + + // now process the request + + firstEntry = auditLogSeparator(auditDetails, firstEntry); + + auditDetails.append("{role: "); + if (!processRole(con, originalRole, provSvcDomain, trustedName, role, + getPrincipalYrn(ctx), auditRef, false, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("unable to put role: " + trustedRole, caller); + } + + String policyYRN = ZMSUtils.policyResourceName(provSvcDomain, trustedName); + StringBuilder resourceYRN = new StringBuilder(256); + resourceYRN.append(provSvcDomain).append(":service.") + .append(ZMSUtils.getTenantResourceGroupRolePrefix(provSvcName, tenantDomain, resourceGroup)) + .append('*'); + List assertions = Arrays.asList( + new Assertion().setRole(trustedRole) + .setResource(resourceYRN.toString()) + .setAction(tenantAction)); + + Policy policy = new Policy().setName(policyYRN).setAssertions(assertions); + + if (LOG.isInfoEnabled()) { + LOG.info(caller + ": add trust policy to domain " + provSvcDomain + + ": " + trustedRole + " -> " + policy); + } + + // retrieve our original policy + + Policy originalPolicy = getPolicy(con, provSvcDomain, policyYRN); + + // now process the request + + auditDetails.append(", policy: "); + if (!processPolicy(con, originalPolicy, provSvcDomain, trustedName, policy, false, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("unable to put policy: " + policy.getName(), caller); + } + auditDetails.append('}'); + } + + // update our domain time-stamp and save changes + + saveChanges(con, provSvcDomain); + + // audit log the request + + auditLogRequest(ctx, provSvcDomain, auditRef, caller, ZMSConsts.HTTP_PUT, + tenantDomain, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void addAssumeRolePolicy(ObjectStoreConnection con, String rolePrefix, String trustedRolePrefix, String role, + List roleMembers, String tenantDomain, String admin, String auditRef, + StringBuilder auditDetails, String caller) { + + // first create the role in the domain. We're going to create it + // only if the role does not already exist + + String roleName = rolePrefix + role; + String roleYRN = ZMSUtils.roleResourceName(tenantDomain, roleName); + + // retrieve our original role in case one exists + + Role originalRole = getRole(con, tenantDomain, roleName, false, false); + + // we need to add the original role members to the new one + + if (originalRole != null && originalRole.getMembers() != null) { + roleMembers.addAll(originalRole.getMembers()); + } + + // now process the request + + Role roleObj = new Role().setName(roleYRN).setMembers(roleMembers); + auditDetails.append("{role: "); + if (!processRole(con, originalRole, tenantDomain, roleName, roleObj, + admin, auditRef, false, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("unable to put role: " + roleName, caller); + } + + // now create the corresponding policy. We're going to create it + // only if the policy does not exist otherwise we'll just + // add a new assertion + + String policyName = "tenancy." + roleName; + String policyYRN = ZMSUtils.policyResourceName(tenantDomain, policyName); + String serviceRoleYRN = trustedRolePrefix + role; + Assertion assertion = new Assertion().setRole(roleYRN) + .setResource(serviceRoleYRN).setAction(ZMSConsts.ACTION_ASSUME_ROLE) + .setEffect(AssertionEffect.ALLOW); + + if (LOG.isInfoEnabled()) { + LOG.info("executePutProviderRoles: ---- ASSUME_ROLE policyName is " + policyName); + } + + // retrieve our original policy + + Policy originalPolicy = getPolicy(con, tenantDomain, policyName); + + // we need to add the original policy assertions to the new one + + List newAssertions = new ArrayList<>(); + newAssertions.add(assertion); + if (originalPolicy != null && originalPolicy.getAssertions() != null) { + newAssertions.addAll(originalPolicy.getAssertions()); + } + + // now process the request + + Policy assumeRolePolicy = new Policy().setName(policyYRN).setAssertions(newAssertions); + + auditDetails.append(", policy: "); + if (!processPolicy(con, originalPolicy, tenantDomain, policyName, assumeRolePolicy, + false, auditDetails)) { + con.rollbackChanges(); + throw ZMSUtils.internalServerError("unable to put policy: " + + assumeRolePolicy.getName(), caller); + } + auditDetails.append('}'); + } + + void executePutProviderRoles(ResourceContext ctx, String tenantDomain, String provSvcDomain, + String provSvcName, String resourceGroup, List roles, String auditRef, String caller) { + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, tenantDomain, auditRef, caller); + + // we're going to create a separate role for each one of tenant roles returned + // based on its action and set the caller as a member in each role + + String principalYrn = getPrincipalYrn(ctx); + List roleMembers = new ArrayList<>(); + if (principalYrn != null) { + roleMembers.add(principalYrn); + } + + // now set up the roles and policies for all the provider roles returned. + + String rolePrefix = ZMSUtils.getProviderResourceGroupRolePrefix(provSvcDomain, provSvcName, + resourceGroup); + String trustedRolePrefix = ZMSUtils.getTrustedResourceGroupRolePrefix(provSvcDomain, + provSvcName, tenantDomain, resourceGroup); + + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditDetails.append("{put-provider-roles: ["); + boolean firstEntry = true; + + for (String role : roles) { + + role = role.toLowerCase(); + + if (LOG.isInfoEnabled()) { + LOG.info("executePutProviderRoles: provision ASSUME_ROLE policy for access remote role in " + + provSvcDomain + "." + provSvcName + ": " + resourceGroup + "." + role); + } + + firstEntry = auditLogSeparator(auditDetails, firstEntry); + + addAssumeRolePolicy(con, rolePrefix, trustedRolePrefix, role, roleMembers, + tenantDomain, principalYrn, auditRef, auditDetails, caller); + } + + auditDetails.append("]}"); + + // update our domain time-stamp and save changes + + saveChanges(con, tenantDomain); + + // audit log the request + + auditLogRequest(ctx, tenantDomain, auditRef, caller, ZMSConsts.HTTP_PUT, + provSvcDomain, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executeDeleteTenancy(ResourceContext ctx, String tenantDomain, String provSvcDomain, + String provSvcName, String resourceGroup, String auditRef, String caller) { + + // create list of policies and delete them from the tenant domain + // have to get all policies that match "tenant..*" + // ex: tenancy.weather.storage.admin + + String rnamePrefix = ZMSUtils.getProviderResourceGroupRolePrefix(provSvcDomain, provSvcName, + resourceGroup); + + StringBuilder pnamePrefixBuilder = new StringBuilder(256); + pnamePrefixBuilder.append("tenancy.").append(rnamePrefix); + String pnamePrefix = pnamePrefixBuilder.toString(); + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, tenantDomain, auditRef, caller); + + // first let's process and remove any policies that start with our + // provider prefix + + List pnames = con.listPolicies(tenantDomain, null); + for (String pname : pnames) { + if (!pname.startsWith(pnamePrefix)) { + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": --ignore policy " + pname); + } + continue; + } + + if (LOG.isInfoEnabled()) { + LOG.info(caller + ": --delete policy " + pname); + } + + con.deletePolicy(tenantDomain, pname); + } + + // now we're going to find any roles that have the provider prefix as + // well but we're going to be careful about removing them. We'll check + // and if we have no more policies referencing them then we'll remove + + List rnames = con.listRoles(tenantDomain); + for (String rname : rnames) { + if (!rname.startsWith(rnamePrefix)) { + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": --ignore role " + rname); + } + continue; + } + + if (!con.listPolicies(tenantDomain, rname).isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": --ignore role " + rname + " due to active references"); + } + continue; + } + + if (LOG.isInfoEnabled()) { + LOG.info(caller + ": --delete role " + rname); + } + + con.deleteRole(tenantDomain, rname); + } + + // update our domain time-stamp and save changes + + saveChanges(con, tenantDomain); + + // audit log the request + + auditLogRequest(ctx, tenantDomain, auditRef, caller, ZMSConsts.HTTP_DELETE, + ZMSUtils.entityResourceName(provSvcDomain, provSvcName), null); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + void executeDeleteTenantRoles(ResourceContext ctx, String provSvcDomain, String provSvcName, + String tenantDomain, String resourceGroup, String auditRef, String caller) { + + // look for this tenants roles, ex: storage.tenant.sports.reader + + String rolePrefix = ZMSUtils.getTenantResourceGroupRolePrefix(provSvcName, tenantDomain, resourceGroup); + + int retryCount = defaultRetryCount; + do { + try (ObjectStoreConnection con = store.getConnection(false)) { + + // first verify that auditing requirements are met + + checkDomainAuditEnabled(con, provSvcDomain, auditRef, caller); + + // find roles and policies matching the prefix + + List rnames = con.listRoles(provSvcDomain); + StringBuilder auditDetails = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + auditDetails.append("{tenant-roles: ["); + boolean firstEntry = true; + for (String rname: rnames) { + if (isTrustRoleForTenant(con, provSvcDomain, rname, rolePrefix, tenantDomain)) { + + // good, its exactly what we are looking for + + con.deleteRole(provSvcDomain, rname); + con.deletePolicy(provSvcDomain, rname); + firstEntry = auditLogString(auditDetails, rname, firstEntry); + } + } + auditDetails.append("]}"); + + // update our domain time-stamp and save changes + + saveChanges(con, provSvcDomain); + + // audit log the request + + auditLogRequest(ctx, tenantDomain, auditRef, caller, ZMSConsts.HTTP_DELETE, + provSvcDomain, auditDetails.toString()); + + return; + + } catch (ResourceException ex) { + if (!shouldRetryOperation(ex, retryCount)) { + throw ex; + } + } + retryCount -= 1; + } while (retryCount > 0); + } + + boolean isTrustRoleForTenant(ObjectStoreConnection con, String provSvcDomain, String roleName, + String rolePrefix, String tenantDomain) { + + // first make sure the role name starts with the given prefix + + if (!isTenantRolePrefixMatch(con, roleName, rolePrefix, tenantDomain)) { + return false; + } + + Role role = con.getRole(provSvcDomain, roleName); + if (role == null) { + return false; + } + + // ensure it is a trust role for the tenant + + String trustDom = role.getTrust(); + if (trustDom != null && trustDom.equals(tenantDomain)) { + return true; + } + + return false; + } + + boolean isTrustRoleForTenant(String provSvcDomain, String roleName, String rolePrefix, + String tenantDomain) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return isTrustRoleForTenant(con, provSvcDomain, roleName, rolePrefix, tenantDomain); + } + } + + boolean isTenantRolePrefixMatch(String roleName, String rolePrefix, String tenantDomain) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return isTenantRolePrefixMatch(con, roleName, rolePrefix, tenantDomain); + } + } + + boolean isTenantRolePrefixMatch(ObjectStoreConnection con, String roleName, String rolePrefix, String tenantDomain) { + + if (LOG.isDebugEnabled()) { + LOG.debug("isTenantRolePrefixMatch: role-name=" + roleName + ", role-prefix=" + + rolePrefix + ", tenant-domain=" + tenantDomain); + } + + // first make sure the role name starts with the given prefix + + if (!roleName.startsWith(rolePrefix)) { + return false; + } + + // also make sure that the last part after the prefix + // does not include other components which could + // indicate support for subdomains and resource groups + // this check is only done if we have no tenantDomain + // specified since that indicates we're processing a resource + // group operation + + if (tenantDomain == null) { + if (roleName.indexOf('.', rolePrefix.length()) != -1) { + return false; + } + } else { + + // otherwise we're going to split the remaining value + // into components. If we have 2 components then we'll + // check if we have a domain for the first component + // if we don't then it's a resource group and as such + // it can be removed otherwise, we'll leave it alone + + String[] comps = roleName.substring(rolePrefix.length()).split("\\."); + if (comps.length == 2) { + + // check to see if we have a subdomain - if we do then + // we're not going to include this role as we don't know + // for sure if this for a resource group or not + + String subDomain = tenantDomain + "." + comps[0]; + + if (LOG.isDebugEnabled()) { + LOG.debug("isTenantRolePrefixMatch: verifying tenant subdomain: " + subDomain); + } + + if (con.getDomain(subDomain) != null) { + return false; + } + } else if (comps.length > 2) { + + // if we have more than 2 subcomponents then we're + // definitely not dealing with resource groups + + return false; + } + } + + return true; + } + + AthenzDomain getAthenzDomain(String domainName) { + + // first check to see if we our data is in the cache + + AthenzDomain athenzDomain = getAthenzDomainFromCache(domainName); + if (athenzDomain != null) { + return athenzDomain; + } + + try (ObjectStoreConnection con = store.getConnection(true)) { + athenzDomain = con.getAthenzDomain(domainName); + } + + if (athenzDomain != null) { + DataCache dataCache = new DataCache(athenzDomain, athenzDomain.getDomain().getModified().millis()); + cacheStore.put(domainName, dataCache); + } + + return athenzDomain; + } + + DomainModifiedList listModifiedDomains(long modifiedSince) { + + try (ObjectStoreConnection con = store.getConnection(true)) { + return con.listModifiedDomains(modifiedSince); + } + } + + boolean auditLogSeparator(StringBuilder auditDetails, boolean firstEntry) { + if (!firstEntry) { + auditDetails.append(','); + } + // regardless of the current state, the new state is no + // longer the first entry so we return false + return false; + } + + void auditLogStrings(StringBuilder auditDetails, String label, Collection values) { + auditDetails.append(", ").append(label).append(": ["); + boolean firstEntry = true; + for (String value : values) { + firstEntry = auditLogString(auditDetails, value, firstEntry); + } + auditDetails.append(']'); + } + + boolean auditLogString(StringBuilder auditDetails, String value, boolean firstEntry) { + firstEntry = auditLogSeparator(auditDetails, firstEntry); + auditDetails.append('\"').append(value).append('\"'); + return firstEntry; + } + + void auditLogPublicKeyEntries(StringBuilder auditDetails, String label, List values) { + auditDetails.append(", ").append(label).append(": ["); + boolean firstEntry = true; + for (PublicKeyEntry value : values) { + firstEntry = auditLogPublicKeyEntry(auditDetails, value, firstEntry); + } + auditDetails.append(']'); + } + + void auditLogPublicKeyEntries(StringBuilder auditDetails, String label, Set values, + Map publicKeysMap) { + auditDetails.append(", ").append(label).append(": ["); + boolean firstEntry = true; + for (String value : values) { + firstEntry = auditLogPublicKeyEntry(auditDetails, publicKeysMap.get(value), firstEntry); + } + auditDetails.append(']'); + } + + void auditLogPublicKeyEntries(StringBuilder auditDetails, String label, Set values) { + auditDetails.append(", ").append(label).append(": ["); + boolean firstEntry = true; + for (String value : values) { + firstEntry = auditLogPublicKeyEntry(auditDetails, value, firstEntry); + } + auditDetails.append(']'); + } + + boolean auditLogPublicKeyEntry(StringBuilder auditDetails, PublicKeyEntry publicKey, boolean firstEntry) { + firstEntry = auditLogSeparator(auditDetails, firstEntry); + auditDetails.append("{key: \"").append(publicKey.getKey()) + .append("\", id: \"").append(publicKey.getId()).append("\"}"); + return firstEntry; + } + + boolean auditLogPublicKeyEntry(StringBuilder auditDetails, String publicKeyId, boolean firstEntry) { + firstEntry = auditLogSeparator(auditDetails, firstEntry); + auditDetails.append("{id: \"").append(publicKeyId).append("\"}"); + return firstEntry; + } + + void auditLogAssertions(StringBuilder auditDetails, String label, Collection values) { + auditDetails.append(", ").append(label).append(": ["); + boolean firstEntry = true; + for (Assertion value : values) { + firstEntry = auditLogAssertion(auditDetails, value, firstEntry); + } + auditDetails.append(']'); + } + + boolean auditLogAssertion(StringBuilder auditDetails, Assertion assertion, boolean firstEntry) { + firstEntry = auditLogSeparator(auditDetails, firstEntry); + String assertionEffect = "ALLOW"; + if (assertion.getEffect() != null) { + assertionEffect = assertion.getEffect().toString(); + } + auditDetails.append("{role: \"").append(assertion.getRole()) + .append("\", action: \"").append(assertion.getAction()) + .append("\", effect: \"").append(assertionEffect) + .append("\", resource: \"").append(assertion.getResource()) + .append("\"}"); + return firstEntry; + } + + void auditLogDomain(StringBuilder auditDetails, Domain domain) { + auditDetails.append("{description: \"").append(domain.getDescription()) + .append("\", org: \"").append(domain.getOrg()) + .append("\", auditEnabled: \"").append(domain.getAuditEnabled()) + .append("\", enabled: \"").append(domain.getEnabled()) + .append("\"}"); + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/GetSignedDomainsResult.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/GetSignedDomainsResult.java new file mode 100644 index 00000000000..ca453e35c77 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/GetSignedDomainsResult.java @@ -0,0 +1,48 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zms; + +import com.yahoo.rdl.*; +import java.util.*; +import javax.ws.rs.core.Response; +import javax.ws.rs.WebApplicationException; + +public final class GetSignedDomainsResult { + private ResourceContext context; + private int code; //normal result + + GetSignedDomainsResult(ResourceContext context) { + this.context = context; + this.code = 0; + } + + public boolean isAsync() { + return false; + } + + public void done(int code, SignedDomains signedDomains, String tag) { + Response resp = Response.status(code).entity(signedDomains) + .header("ETag", tag) + .build(); + throw new WebApplicationException(resp); + } + + public void done(int code) { + done(code, new ResourceError().code(code).message(ResourceException.codeToString(code)), ""); + } + + public void done(int code, String tag) { + done(code, new ResourceError().code(code).message(ResourceException.codeToString(code)), tag); + } + + public void done(int code, Object entity, String tag) { + this.code = code; + //to do: check if the exception is declared, and that the entity is of the declared type + WebApplicationException err = new WebApplicationException(Response.status(code).entity(entity) + .header("ETag", tag) + .build()); + throw err; //not optimal + } + +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceContext.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceContext.java new file mode 100644 index 00000000000..7842050e29f --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceContext.java @@ -0,0 +1,17 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zms; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +// +// ResourceContext +// +public interface ResourceContext { + public HttpServletRequest request(); + public HttpServletResponse response(); + public void authenticate(); + public void authorize(String action, String resource, String trustedDomain); +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceError.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceError.java new file mode 100644 index 00000000000..231c7abdf9d --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceError.java @@ -0,0 +1,24 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zms; + +public class ResourceError { + + public int code; + public String message; + + public ResourceError code(int code) { + this.code = code; + return this; + } + public ResourceError message(String message) { + this.message = message; + return this; + } + + public String toString() { + return "{code: " + code + ", message: \"" + message + "\"}"; + } + +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceException.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceException.java new file mode 100644 index 00000000000..a76e476ac22 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ResourceException.java @@ -0,0 +1,79 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zms; + +public class ResourceException extends RuntimeException { + public final static int OK = 200; + public final static int CREATED = 201; + public final static int ACCEPTED = 202; + public final static int NO_CONTENT = 204; + public final static int MOVED_PERMANENTLY = 301; + public final static int FOUND = 302; + public final static int SEE_OTHER = 303; + public final static int NOT_MODIFIED = 304; + public final static int TEMPORARY_REDIRECT = 307; + public final static int BAD_REQUEST = 400; + public final static int UNAUTHORIZED = 401; + public final static int FORBIDDEN = 403; + public final static int NOT_FOUND = 404; + public final static int CONFLICT = 409; + public final static int GONE = 410; + public final static int PRECONDITION_FAILED = 412; + public final static int UNSUPPORTED_MEDIA_TYPE = 415; + public final static int INTERNAL_SERVER_ERROR = 500; + public final static int NOT_IMPLEMENTED = 501; + + public final static int SERVICE_UNAVAILABLE = 503; + + public static String codeToString(int code) { + switch (code) { + case OK: return "OK"; + case CREATED: return "Created"; + case ACCEPTED: return "Accepted"; + case NO_CONTENT: return "No Content"; + case MOVED_PERMANENTLY: return "Moved Permanently"; + case FOUND: return "Found"; + case SEE_OTHER: return "See Other"; + case NOT_MODIFIED: return "Not Modified"; + case TEMPORARY_REDIRECT: return "Temporary Redirect"; + case BAD_REQUEST: return "Bad Request"; + case UNAUTHORIZED: return "Unauthorized"; + case FORBIDDEN: return "Forbidden"; + case NOT_FOUND: return "Not Found"; + case CONFLICT: return "Conflict"; + case GONE: return "Gone"; + case PRECONDITION_FAILED: return "Precondition Failed"; + case UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type"; + case INTERNAL_SERVER_ERROR: return "Internal Server Error"; + case NOT_IMPLEMENTED: return "Not Implemented"; + default: return "" + code; + } + } + + int code; + Object data; + + public ResourceException(int code) { + this(code, new ResourceError().code(code).message(codeToString(code))); + } + + public ResourceException(int code, Object data) { + super("ResourceException (" + code + "): " + data); + this.code = code; + this.data = data; + } + + public int getCode() { + return code; + } + + public Object getData() { + return data; + } + + public T getData(Class cl) { + return cl.cast(data); + } + +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMS.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMS.java new file mode 100644 index 00000000000..54ae6b52667 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMS.java @@ -0,0 +1,241 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zms; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.eclipse.jetty.server.HttpConfiguration; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.common.metrics.MetricFactory; +import com.yahoo.athenz.common.server.log.AuditLogFactory; +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.common.server.rest.Http.AuthorityList; +import com.yahoo.athenz.zms.pkey.PrivateKeyStoreFactory; + +import java.net.InetAddress; + +public class ZMS { + + private static final Logger LOG = LoggerFactory.getLogger(ZMS.class); + + private static final String ZMS_PRINCIPAL_AUTHORITY_CLASS = "com.yahoo.athenz.auth.impl.PrincipalAuthority"; + private static final String ZMS_PKEY_STORE_CLASS = "com.yahoo.athenz.zms.pkey.file.FilePrivateKeyStoreFactory"; + + // This String is used to create the desired AuditLogMsgBuilder object. + // Its OK if its null, we will just get the default msg builder. + // + private static String AUDIT_LOG_MSG_BLDR_CLASS; + static { + try { + AUDIT_LOG_MSG_BLDR_CLASS = System.getProperty(ZMSConsts.ZMS_PROP_AUDIT_LOG_MSG_BLDR_CLASS); + // test the class to ensure it is valid + try { + @SuppressWarnings("unused") + AuditLogMsgBuilder msgBldr = AuditLogFactory.getMsgBuilder(AUDIT_LOG_MSG_BLDR_CLASS); + } catch (Exception exc) { + LOG.warn("AuditLogMsgBuilder: Cannot instantiate message builder class from=" + + AUDIT_LOG_MSG_BLDR_CLASS + ", therefore will use default log message builder class instead.", exc); + AUDIT_LOG_MSG_BLDR_CLASS = null; + } + } catch (Exception exc) { + LOG.warn("Failed to get the audit log message builder class using property=" + + ZMSConsts.ZMS_PROP_AUDIT_LOG_MSG_BLDR_CLASS + + ", ZMS will use the default log message builder class instead.", exc); + AUDIT_LOG_MSG_BLDR_CLASS = null; + } + } + + private static final AuditLogger AUDITLOG = getAuditLogger(); + + // Create an AuditLogger + // + static AuditLogger getAuditLogger() { + String auditLoggerClassName = System.getProperty(ZMSConsts.ZMS_PROP_AUDIT_LOGGER_CLASS); + String auditLoggerClassNameParam = System.getProperty(ZMSConsts.ZMS_PROP_AUDIT_LOGGER_CLASS_PARAM); + AuditLogger auditLog = null; + try { + if (auditLoggerClassNameParam != null) { + auditLog = AuditLogFactory.getLogger(auditLoggerClassName, auditLoggerClassNameParam); + } else { + auditLog = AuditLogFactory.getLogger(auditLoggerClassName); + } + } catch (Exception exc) { + LOG.warn("Failed to create audit logger from class=" + + auditLoggerClassName + ", ZMS will use the default logger instead.", exc); + auditLog = AuditLogFactory.getLogger(); + } + return auditLog; + } + + static String getServerHostName() { + + String serverHostName = System.getProperty(ZMSConsts.ZMS_PROP_HOSTNAME); + if (serverHostName == null || serverHostName.isEmpty()) { + try { + InetAddress localhost = java.net.InetAddress.getLocalHost(); + serverHostName = localhost.getCanonicalHostName(); + } catch (java.net.UnknownHostException e) { + LOG.info("Unable to determine local hostname: " + e.getMessage()); + serverHostName = "localhost"; + } + } + + return serverHostName; + } + + static Authority getAuthority(String className) { + + LOG.debug("Loading authority {}...", className); + + Authority authority = null; + try { + authority = (Authority) Class.forName(className).newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + LOG.error("Invalid Authority class: " + className + " error: " + e.getMessage()); + return null; + } + return authority; + } + + static int getPortNumber(String property, int defaultValue) { + + String propValue = System.getProperty(property); + if (propValue == null) { + return defaultValue; + } + + int port = defaultValue; + try { + + // first try to convert the string property to integer + + port = Integer.parseInt(propValue); + + // now verify that it's a valid port number + + if (port < 0 || port > 65535) { + throw new NumberFormatException(); + } + + } catch (NumberFormatException ex) { + LOG.info("invalid port: " + propValue + ". Using default port: " + defaultValue); + port = defaultValue; + } + + return port; + } + + public static ZMSJettyContainer createJettyContainer() { + + ZMSJettyContainer container = null; + + String root = System.getenv("ROOT"); + if (root == null) { + root = "/home/athenz"; + } + + String homeDir = System.getProperty(ZMSConsts.ZMS_PROP_HOME, root + "/var/zms_server"); + String jdbcStore = System.getProperty(ZMSConsts.ZMS_PROP_JDBC_STORE); + String dataStoreContext = jdbcStore == null ? homeDir : jdbcStore; + + // retrieve our http and https port numbers + + int httpPort = getPortNumber(ZMSConsts.ZMS_PROP_HTTP_PORT, ZMSConsts.ZMS_HTTP_PORT_DEFAULT); + int httpsPort = getPortNumber(ZMSConsts.ZMS_PROP_HTTPS_PORT, ZMSConsts.ZMS_HTTPS_PORT_DEFAULT); + + String serverHostName = getServerHostName(); + + // get our authorities + + String authListConfig = System.getProperty(ZMSConsts.ZMS_PROP_AUTHORITY_CLASSES, ZMS_PRINCIPAL_AUTHORITY_CLASS); + AuthorityList authorities = new AuthorityList(); + + String[] authorityList = authListConfig.split(","); + for (int idx = 0; idx < authorityList.length; idx++) { + Authority authority = getAuthority(authorityList[idx]); + if (authority == null) { + return null; + } + authority.initialize(); + authorities.add(authority); + } + + String pkeyFactoryClass = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_STORE_CLASS, ZMS_PKEY_STORE_CLASS); + PrivateKeyStoreFactory pkeyFactory = null; + try { + pkeyFactory = (PrivateKeyStoreFactory) Class.forName(pkeyFactoryClass).newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + LOG.error("Invalid PrivateKeyStoreFactory class: " + pkeyFactoryClass + + " error: " + e.getMessage()); + return null; + } + + String metricFactoryClass = System.getProperty(ZMSConsts.ZMS_PROP_METRIC_FACTORY_CLASS, ZMSConsts.ZMS_METRIC_FACTORY_CLASS); + boolean statsEnabled = Boolean.parseBoolean(System.getProperty(ZMSConsts.ZMS_PROP_STATS_ENABLED, "false")); + if (!statsEnabled && !metricFactoryClass.equals(ZMSConsts.ZMS_METRIC_FACTORY_CLASS)) { + LOG.warn("Override users metric factory property with default since stats are disabled"); + metricFactoryClass = ZMSConsts.ZMS_METRIC_FACTORY_CLASS; + } + + MetricFactory metricFactory = null; + try { + metricFactory = (MetricFactory) Class.forName(metricFactoryClass).newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + LOG.error("Invalid MetricFactory class: " + metricFactoryClass + + " error: " + e.getMessage()); + return null; + } + + ZMSServerImpl core = new ZMSServerImpl(serverHostName, dataStoreContext, + pkeyFactory, metricFactory, AUDITLOG, AUDIT_LOG_MSG_BLDR_CLASS, authorities); + + container = new ZMSJettyContainer(AUDITLOG); + container.resource(ZMSResources.class); + container.delegate(ZMSHandler.class, core.getInstance()); + container.authorizer(core.getAuthorizer()); + container.setBanner("http://" + serverHostName + " http port: " + httpPort + " https port: " + httpsPort); + + int maxThreads = Integer.parseInt(System.getProperty(ZMSConsts.ZMS_PROP_MAX_THREADS, "1024")); + container.createServer(maxThreads); + + HttpConfiguration httpConfig = container.newHttpConfiguration(httpsPort); + container.addHTTPConnectors(httpConfig, httpPort, httpsPort); + container.addServletHandlers(homeDir, serverHostName); + + container.addRequestLogHandler(root); + + return container; + } + + public static void main(String [] args) throws Exception { + + System.getProperties().remove("socksProxyHost"); + + try { + ZMSJettyContainer container = createJettyContainer(); + container.run(null); + } catch (Exception exc) { + + // log that we are shutting down, re-throw the exception + + LOG.error("Startup failure. Shutting down: " + exc.getMessage()); + throw exc; + } + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSConsts.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSConsts.java new file mode 100644 index 00000000000..26f0b05dded --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSConsts.java @@ -0,0 +1,184 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +/** + * Contains constants shared by classes throughout the service. + **/ +public final class ZMSConsts { + + // System property names with defaults(where applicable) + // + public static final String ZMS_PROP_HOME = "athenz.zms.home"; + public static final String ZMS_PROP_HOSTNAME = "athenz.zms.hostname"; + public static final String ZMS_PROP_DOMAIN_ADMIN = "athenz.zms.domain_admin"; + public static final String ZMS_PROP_HTTPS_PORT = "athenz.zms.tls_port"; + public static final String ZMS_PROP_HTTP_PORT = "athenz.zms.port"; + + public static final String ZMS_PROP_ACCESS_LOG_DIR = "athenz.zms.access_log_dir"; + public static final String ZMS_PROP_STATS_ENABLED = "athenz.zms.enable_stats"; + public static final String ZMS_PROP_VIRTUAL_DOMAIN = "athenz.zms.virtual_domain_support"; + public static final String ZMS_PROP_VIRTUAL_DOMAIN_LIMIT = "athenz.zms.virtual_domain_limit"; + public static final String ZMS_PROP_READ_ONLY_MODE = "athenz.zms.read_only_mode"; + public static final String ZMS_PROP_DOMAIN_NAME_MAX_SIZE = "athenz.zms.domain_name_max_len"; + public static final String ZMS_PROP_METRIC_FACTORY_CLASS = "athenz.zms.metric_class"; + + public static final String ZMS_PROP_CONFLICT_RETRY_COUNT = "athenz.zms.request_conflict_retry_count"; + public static final String ZMS_PROP_CONFLICT_RETRY_SLEEP_TIME = "athenz.zms.request_conflict_retry_sleep_time"; + + public static final String ZMS_PROP_PRIVATE_KEY = "athenz.zms.privatekey"; + public static final String ZMS_PROP_PRIVATE_KEY_ID = "athenz.zms.privatekey_id"; + public static final String ZMS_PROP_PRIVATE_KEY_NAME = "athenz.zms.privatekey_name"; + public static final String ZMS_PROP_PUBLIC_KEY = "athenz.zms.publickey"; + public static final String ZMS_PROP_PUBLIC_KEY_NAME = "athenz.zms.publickey_name"; + + public static final String ZMS_PROP_JDBC_STORE = "athenz.zms.jdbcstore"; + public static final String ZMS_PROP_MAX_THREADS = "athenz.zms.http_max_threads"; + public static final String ZMS_PROP_AUTHORITY_CLASSES = "athenz.zms.authority_classes"; + + public static final String ZMS_PROP_TIMEOUT = "athenz.zms.user_token_timeout"; + public static final String ZMS_PROP_SIGNED_POLICY_TIMEOUT = "athenz.zms.signed_policy_timeout"; + public static final String ZMS_PROP_AUTHZ_SERVICE_FNAME = "athenz.zms.authz_service_fname"; + public static final String ZMS_PROP_SOLUTION_TEMPLATE_FNAME = "athenz.zms.solution_templates_fname"; + + public static final String ZMS_PROP_USER_DOMAIN = "athenz.user_domain"; + + public static final String ZMS_PROP_ACCESS_LOG_RETAIN_DAYS = "athenz.zms.access_log_retain_days"; + public static final String ZMS_PROP_ACCESS_LOG_NAME = "athenz.zms.access_log_name"; + public static final String ZMS_PROP_ACCESS_SLF4J_LOGGER = "athenz.zms.access_slf4j_logger"; + + public static final String ZMS_PROP_KEYSTORE_PASSWORD = "athenz.zms.ssl_key_store_password"; + public static final String ZMS_PROP_KEYMANAGER_PASSWORD = "athenz.zms.ssl_key_manager_password"; + public static final String ZMS_PROP_TRUSTSTORE_PASSWORD = "athenz.zms.ssl_trust_store_password"; + public static final String ZMS_PROP_KEYSTORE_PATH = "athenz.zms.ssl_key_store"; + public static final String ZMS_PROP_KEYSTORE_TYPE = "athenz.zms.ssl_key_store_type"; + public static final String ZMS_PROP_TRUSTSTORE_PATH = "athenz.zms.ssl_trust_store"; + public static final String ZMS_PROP_TRUSTSTORE_TYPE = "athenz.zms.ssl_trust_store_type"; + public static final String ZMS_PROP_EXCLUDED_CIPHER_SUITES = "athenz.zms.ssl_excluded_cipher_suites"; + public static final String ZMS_PROP_EXCLUDED_PROTOCOLS = "athenz.zms.ssl_excluded_protocols"; + public static final String ZMS_PROP_IDLE_TIMEOUT = "athenz.zms.http_idle_timeout"; + public static final String ZMS_PROP_SEND_SERVER_VERSION = "athenz.zms.http_send_server_version"; + public static final String ZMS_PROP_SEND_DATE_HEADER = "athenz.zms.http_send_date_header"; + public static final String ZMS_PROP_OUTPUT_BUFFER_SIZE = "athenz.zms.http_output_buffer_size"; + public static final String ZMS_PROP_REQUEST_HEADER_SIZE = "athenz.zms.http_reqeust_header_size"; + public static final String ZMS_PROP_RESPONSE_HEADER_SIZE = "athenz.zms.http_response_header_size"; + public static final String ZMS_PROP_LISTEN_HOST = "athenz.zms.listen_host"; + public static final String ZMS_PROP_KEEP_ALIVE = "athenz.zms.keep_alive"; + public static final String ZMS_PROP_DB_TABLE = "athenz.zms.zms_dbtable"; + public static final String ZMS_PROP_PROVIDER_ENDPOINTS = "athenz.zms.provider_endpoints"; + public static final String ZMS_PROP_PRODUCT_ID_SUPPORT = "athenz.zms.product_id_support"; + + // properties used to over-ride default Audit logger + + public static final String ZMS_PROP_AUDIT_LOGGER_CLASS = "athenz.zms.audit_logger_class"; + public static final String ZMS_PROP_AUDIT_LOGGER_CLASS_PARAM = "athenz.zms.audit_logger_class_param"; + public static final String ZMS_PROP_AUDIT_LOG_MSG_BLDR_CLASS = "athenz.zms.audit_log_msg_builder_class"; + + public static final String ZMS_PROP_PRIVATE_KEY_STORE_CLASS = "athenz.zms.private_key_store_class"; + + public static final String ZMS_METRIC_FACTORY_CLASS = "com.yahoo.athenz.common.metrics.impl.NoOpMetricFactory"; + + public static final String ZMS_UNKNOWN_DOMAIN = "unknown_domain"; + public static final String ZMS_INVALID_DOMAIN = "invalid_domain"; + + public static final int ZMS_HTTPS_PORT_DEFAULT = 0; + public static final int ZMS_HTTP_PORT_DEFAULT = 10080; + public static final String ZMS_STATS_SCOREBOARD = "zms_core"; + + public static final String ZMS_DOMAIN_NAME_MAX_SIZE_DEFAULT = "128"; + + public static final String USER_DOMAIN = "user"; + public static final String USER_DOMAIN_PREFIX = "user."; + + public static final String HTTP_ORIGIN = "Origin"; + public static final String HTTP_RFC1123_DATE_FORMAT = "EEE, d MMM yyyy HH:mm:ss zzz"; + public static final String HTTP_DATE_GMT_ZONE = "GMT"; + + public static final String HTTP_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + public static final String HTTP_ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + public static final String HTTP_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + public static final String HTTP_ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; + public static final String HTTP_ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + public static final String HTTP_ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; + + public static final String LOCALHOST = "localhost"; + public static final String HTTP_SCHEME = "http"; + public static final String HTTPS_SCHEME = "https"; + + public static final String DB_COLUMN_DESCRIPTION = "description"; + public static final String DB_COLUMN_ORG = "org"; + public static final String DB_COLUMN_UUID = "uuid"; + public static final String DB_COLUMN_ENABLED = "enabled"; + public static final String DB_COLUMN_AUDIT_ENABLED = "audit_enabled"; + public static final String DB_COLUMN_MODIFIED = "modified"; + public static final String DB_COLUMN_NAME = "name"; + public static final String DB_COLUMN_TRUST = "trust"; + public static final String DB_COLUMN_MEMBER = "member"; + public static final String DB_COLUMN_ROLE = "role"; + public static final String DB_COLUMN_RESOURCE = "resource"; + public static final String DB_COLUMN_ACTION = "action"; + public static final String DB_COLUMN_EFFECT = "effect"; + public static final String DB_COLUMN_KEY_VALUE = "key_value"; + public static final String DB_COLUMN_KEY_ID = "key_id"; + public static final String DB_COLUMN_SVC_USER = "svc_user"; + public static final String DB_COLUMN_SVC_GROUP = "svc_group"; + public static final String DB_COLUMN_EXECTUABLE = "executable"; + public static final String DB_COLUMN_PROVIDER_ENDPOINT = "provider_endpoint"; + public static final String DB_COLUMN_VALUE = "value"; + public static final String DB_COLUMN_DOMAIN_ID = "domain_id"; + public static final String DB_COLUMN_DOMAIN = "domain"; + public static final String DB_COLUMN_ACCOUNT = "account"; + public static final String DB_COLUMN_PRODUCT_ID = "ypm_id"; + public static final String DB_COLUMN_ADMIN = "admin"; + public static final String DB_COLUMN_CREATED = "created"; + public static final String DB_COLUMN_AUDIT_REF = "audit_ref"; + public static final String DB_COLUMN_ROLE_NAME = "role_name"; + public static final String DB_COLUMN_ASSERT_DOMAIN_ID = "assert_domain_id"; + public static final String DB_COLUMN_ASSERT_ID = "assertion_id"; + + public static final String ADMIN_POLICY_NAME = "admin"; + public static final String ADMIN_ROLE_NAME = "admin"; + + public static final String ROLE_PREFIX = "role."; + public static final String POLICY_PREFIX = "policy."; + + public static final String ASSERTION_EFFECT_ALLOW = "ALLOW"; + public static final String ACTION_ASSUME_ROLE = "assume_role"; + public static final String ACTION_ASSUME_AWS_ROLE = "assume_aws_role"; + public static final String ACTION_UPDATE = "update"; + + public static final String OBJECT_DOMAIN = "domain"; + public static final String OBJECT_ROLE = "role"; + public static final String OBJECT_POLICY = "policy"; + public static final String OBJECT_SERVICE = "service"; + public static final String OBJECT_PRINCIPAL = "principal"; + public static final String OBJECT_HOST = "host"; + + // HTTP operation types used in metrics + public static final String HTTP_GET = "GET"; + public static final String HTTP_PUT = "PUT"; + public static final String HTTP_POST = "POST"; + public static final String HTTP_DELETE = "DELETE"; + public static final String HTTP_OPTIONS = "OPTIONS"; + public static final String HTTP_REQUEST = "REQUEST"; + + public static final String STR_DEF_ROOT = "/home/athenz"; + public static final String STR_ENV_ROOT = "ROOT"; + public static final String STR_JAR_RESOURCE = "JAR_RESOURCE:"; + + public static final int STRING_BLDR_SIZE_DEFAULT = 512; +} + diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSDaemon.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSDaemon.java new file mode 100644 index 00000000000..1d50296a145 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSDaemon.java @@ -0,0 +1,40 @@ +package com.yahoo.athenz.zms; +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import org.apache.commons.daemon.Daemon; +import org.apache.commons.daemon.DaemonContext; + +public class ZMSDaemon implements Daemon { + + private String[] args = null; + + public void init(DaemonContext context) throws Exception { + args = context.getArguments(); + } + + public void start() throws Exception { + if (args == null) { + return; + } + ZMS.main(args); + } + + public void stop() throws Exception { + } + + public void destroy() { + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSHandler.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSHandler.java new file mode 100644 index 00000000000..b70a94eb508 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSHandler.java @@ -0,0 +1,82 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zms; + +import com.yahoo.rdl.*; +import java.util.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +// +// ZMSHandler is the interface that the service implementation must implement +// +public interface ZMSHandler { + public Domain getDomain(ResourceContext context, String domain); + public DomainList getDomainList(ResourceContext context, Integer limit, String skip, String prefix, Integer depth, String account, Integer productId, String roleMember, String roleName, String modifiedSince); + public Domain postTopLevelDomain(ResourceContext context, String auditRef, TopLevelDomain detail); + public Domain postSubDomain(ResourceContext context, String parent, String auditRef, SubDomain detail); + public Domain postUserDomain(ResourceContext context, String name, String auditRef, UserDomain detail); + public TopLevelDomain deleteTopLevelDomain(ResourceContext context, String name, String auditRef); + public SubDomain deleteSubDomain(ResourceContext context, String parent, String name, String auditRef); + public UserDomain deleteUserDomain(ResourceContext context, String name, String auditRef); + public Domain putDomainMeta(ResourceContext context, String name, String auditRef, DomainMeta detail); + public DomainTemplate putDomainTemplate(ResourceContext context, String name, String auditRef, DomainTemplate template); + public DomainTemplateList getDomainTemplateList(ResourceContext context, String name); + public DomainTemplate deleteDomainTemplate(ResourceContext context, String name, String template, String auditRef); + public DomainDataCheck getDomainDataCheck(ResourceContext context, String domainName); + public Entity putEntity(ResourceContext context, String domainName, String entityName, String auditRef, Entity entity); + public Entity getEntity(ResourceContext context, String domainName, String entityName); + public Entity deleteEntity(ResourceContext context, String domainName, String entityName, String auditRef); + public EntityList getEntityList(ResourceContext context, String domainName); + public RoleList getRoleList(ResourceContext context, String domainName, Integer limit, String skip); + public Roles getRoles(ResourceContext context, String domainName, Boolean members); + public Role getRole(ResourceContext context, String domainName, String roleName, Boolean auditLog, Boolean expand); + public Role putRole(ResourceContext context, String domainName, String roleName, String auditRef, Role role); + public Role deleteRole(ResourceContext context, String domainName, String roleName, String auditRef); + public Membership getMembership(ResourceContext context, String domainName, String roleName, String memberName); + public Membership putMembership(ResourceContext context, String domainName, String roleName, String memberName, String auditRef, Membership membership); + public Membership deleteMembership(ResourceContext context, String domainName, String roleName, String memberName, String auditRef); + public DefaultAdmins putDefaultAdmins(ResourceContext context, String domainName, String auditRef, DefaultAdmins defaultAdmins); + public PolicyList getPolicyList(ResourceContext context, String domainName, Integer limit, String skip); + public Policies getPolicies(ResourceContext context, String domainName, Boolean assertions); + public Policy getPolicy(ResourceContext context, String domainName, String policyName); + public Policy putPolicy(ResourceContext context, String domainName, String policyName, String auditRef, Policy policy); + public Policy deletePolicy(ResourceContext context, String domainName, String policyName, String auditRef); + public Assertion getAssertion(ResourceContext context, String domainName, String policyName, Long assertionId); + public Assertion putAssertion(ResourceContext context, String domainName, String policyName, String auditRef, Assertion assertion); + public Assertion deleteAssertion(ResourceContext context, String domainName, String policyName, Long assertionId, String auditRef); + public ServiceIdentity putServiceIdentity(ResourceContext context, String domain, String service, String auditRef, ServiceIdentity detail); + public ServiceIdentity getServiceIdentity(ResourceContext context, String domain, String service); + public ServiceIdentity deleteServiceIdentity(ResourceContext context, String domain, String service, String auditRef); + public ServiceIdentities getServiceIdentities(ResourceContext context, String domainName, Boolean publickeys, Boolean hosts); + public ServiceIdentityList getServiceIdentityList(ResourceContext context, String domainName, Integer limit, String skip); + public PublicKeyEntry getPublicKeyEntry(ResourceContext context, String domain, String service, String id); + public PublicKeyEntry putPublicKeyEntry(ResourceContext context, String domain, String service, String id, String auditRef, PublicKeyEntry publicKeyEntry); + public PublicKeyEntry deletePublicKeyEntry(ResourceContext context, String domain, String service, String id, String auditRef); + public Tenancy putTenancy(ResourceContext context, String domain, String service, String auditRef, Tenancy detail); + public Tenancy getTenancy(ResourceContext context, String domain, String service); + public Tenancy deleteTenancy(ResourceContext context, String domain, String service, String auditRef); + public TenancyResourceGroup putTenancyResourceGroup(ResourceContext context, String domain, String service, String resourceGroup, String auditRef, TenancyResourceGroup detail); + public TenancyResourceGroup deleteTenancyResourceGroup(ResourceContext context, String domain, String service, String resourceGroup, String auditRef); + public TenantRoles putTenantRoles(ResourceContext context, String domain, String service, String tenantDomain, String auditRef, TenantRoles detail); + public TenantRoles getTenantRoles(ResourceContext context, String domain, String service, String tenantDomain); + public TenantRoles deleteTenantRoles(ResourceContext context, String domain, String service, String tenantDomain, String auditRef); + public TenantResourceGroupRoles putTenantResourceGroupRoles(ResourceContext context, String domain, String service, String tenantDomain, String resourceGroup, String auditRef, TenantResourceGroupRoles detail); + public TenantResourceGroupRoles getTenantResourceGroupRoles(ResourceContext context, String domain, String service, String tenantDomain, String resourceGroup); + public TenantResourceGroupRoles deleteTenantResourceGroupRoles(ResourceContext context, String domain, String service, String tenantDomain, String resourceGroup, String auditRef); + public ProviderResourceGroupRoles putProviderResourceGroupRoles(ResourceContext context, String tenantDomain, String provDomain, String provService, String resourceGroup, String auditRef, ProviderResourceGroupRoles detail); + public ProviderResourceGroupRoles getProviderResourceGroupRoles(ResourceContext context, String tenantDomain, String provDomain, String provService, String resourceGroup); + public ProviderResourceGroupRoles deleteProviderResourceGroupRoles(ResourceContext context, String tenantDomain, String provDomain, String provService, String resourceGroup, String auditRef); + public Access getAccess(ResourceContext context, String action, String resource, String domain, String checkPrincipal); + public Access getAccessExt(ResourceContext context, String action, String resource, String domain, String checkPrincipal); + public ResourceAccessList getResourceAccessList(ResourceContext context, String principal, String action); + public void getSignedDomains(ResourceContext context, String domain, String metaOnly, String matchingTag, GetSignedDomainsResult result); + public UserToken getUserToken(ResourceContext context, String userName, String serviceNames); + public UserToken optionsUserToken(ResourceContext context, String userName, String serviceNames); + public ServicePrincipal getServicePrincipal(ResourceContext context); + public ServerTemplateList getServerTemplateList(ResourceContext context); + public Template getTemplate(ResourceContext context, String template); + public Schema getRdlSchema(ResourceContext context); + public ResourceContext newResourceContext(HttpServletRequest request, HttpServletResponse response); +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSImpl.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSImpl.java new file mode 100644 index 00000000000..36abcb46d97 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSImpl.java @@ -0,0 +1,6208 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Schema; +import com.yahoo.rdl.Timestamp; +import com.yahoo.rdl.Validator; +import com.yahoo.rdl.Validator.Result; +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Authorizer; +import com.yahoo.athenz.auth.KeyStore; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.token.PrincipalToken; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.server.log.AuditLogFactory; +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.common.server.rest.Http; +import com.yahoo.athenz.common.server.util.ServletRequestUtil; +import com.yahoo.athenz.common.server.util.StringUtils; +import com.yahoo.athenz.common.utils.SignUtils; +import com.yahoo.athenz.provider.ProviderClient; +import com.yahoo.athenz.provider.Tenant; +import com.yahoo.athenz.provider.TenantResourceGroup; +import com.yahoo.athenz.zms.config.AllowedOperation; +import com.yahoo.athenz.zms.config.AuthorizedService; +import com.yahoo.athenz.zms.config.AuthorizedServices; +import com.yahoo.athenz.zms.config.SolutionTemplates; +import com.yahoo.athenz.zms.store.AthenzDomain; +import com.yahoo.athenz.zms.store.ObjectStore; +import com.yahoo.athenz.zms.utils.ZMSUtils; + +import java.util.HashSet; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.ListIterator; +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.text.ParseException; +import java.text.SimpleDateFormat; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.ws.rs.core.EntityTag; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A reference implementation of ZMS. Uses a StructStore for domain information. + * This class is not public - use the ZMSCore class to access it. + */ +public class ZMSImpl implements Authorizer, KeyStore, ZMSHandler { + + private static final Logger LOG = LoggerFactory.getLogger(ZMSImpl.class); + + private static final String SERVICE_PREFIX = "service."; + private static final String ROLE_PREFIX = "role."; + private static final String POLICY_PREFIX = "policy."; + + private static final String ADMIN_POLICY_NAME = "admin"; + private static final String ADMIN_ROLE_NAME = "admin"; + + private static final String ROLE_FIELD = "role"; + private static final String POLICY_FIELD = "policy"; + private static final String SERVICE_FIELD = "service"; + private static final String TEMPLATE_FIELD = "template"; + private static final String META_FIELD = "meta"; + private static final String DOMAIN_FIELD = "domain"; + + private static final String SYS_AUTH = "sys.auth"; + private static final String ZMS = "zms"; + private static final String USER_TOKEN_DEFAULT_NAME = "_self_"; + + // data validation types + private static final String TYPE_DOMAIN_NAME = "DomainName"; + private static final String TYPE_ENTITY_NAME = "EntityName"; + private static final String TYPE_SIMPLE_NAME = "SimpleName"; + private static final String TYPE_COMPOUND_NAME = "CompoundName"; + private static final String TYPE_RESOURCE_NAME = "ResourceName"; + private static final String TYPE_SERVICE_NAME = "ServiceName"; + private static final String TYPE_ROLE = "Role"; + private static final String TYPE_POLICY = "Policy"; + private static final String TYPE_ASSERTION = "Assertion"; + private static final String TYPE_SERVICE_IDENTITY = "ServiceIdentity"; + private static final String TYPE_TOP_LEVEL_DOMAIN = "TopLevelDomain"; + private static final String TYPE_SUB_DOMAIN = "SubDomain"; + private static final String TYPE_USER_DOMAIN = "UserDomain"; + private static final String TYPE_DOMAIN_META = "DomainMeta"; + private static final String TYPE_DOMAIN_TEMPLATE = "DomainTemplate"; + private static final String TYPE_TENANT_ROLES = "TenantRoles"; + private static final String TYPE_TENANT_RESOURCE_GROUP_ROLES = "TenantResourceGroupRoles"; + private static final String TYPE_PROVIDER_RESOURCE_GROUP_ROLES = "ProviderResourceGroupRoles"; + private static final String TYPE_YRN = "YRN"; + private static final String TYPE_PUBLIC_KEY_ENTRY = "PublicKeyEntry"; + + public static Metric metric; + protected ObjectStore dbStore = null; + protected DBService dbService = null; + protected Class providerClass = null; + protected Schema schema = null; + protected String publicKey = null; + protected PrivateKey privateKey = null; + protected String privateKeyId = "0"; + protected int userTokenTimeout = 3600; + protected boolean virtualDomainSupport = true; + protected boolean productIdSupport = false; + protected int virtualDomainLimit = 2; + protected long signedPolicyTimeout; + protected static String serverHostName = null; + protected static String serverHttpsPort = null; + protected static String serverHttpPort = null; + protected int domainNameMaxLen; + protected AuthorizedServices serverAuthorizedServices = null; + protected static SolutionTemplates serverSolutionTemplates = null; + protected Map serverPublicKeyMap = null; + protected boolean readOnlyMode = false; + protected static Validator validator; + protected static String userDomain; + protected static String userDomainPrefix; + protected String zmsRootDir = null; + protected Http.AuthorityList authorities = null; + protected List providerEndpoints = null; + + // enum to represent our access response since in some cases we want to + // handle domain not founds differently instead of just returning failure + + enum AccessStatus { + ALLOWED, + DENIED, + DENIED_DOMAIN_NOT_FOUND, + DENIED_INVALID_ROLE_TOKEN + } + + enum AthenzObject { + ASSERTION { + void convertToLowerCase(Object obj) { + Assertion assertion = (Assertion) obj; + assertion.setAction(assertion.getAction().toLowerCase()); + assertion.setResource(assertion.getResource().toLowerCase()); + assertion.setRole(assertion.getRole().toLowerCase()); + } + }, + DEFAULT_ADMINS { + void convertToLowerCase(Object obj) { + DefaultAdmins defaultAdmins = (DefaultAdmins) obj; + LIST.convertToLowerCase(defaultAdmins.getAdmins()); + } + }, + DOMAIN_TEMPLATE_LIST { + void convertToLowerCase(Object obj) { + DomainTemplateList templates = (DomainTemplateList) obj; + if (templates != null) { + LIST.convertToLowerCase(templates.getTemplateNames()); + } + } + }, + DOMAIN_TEMPLATE { + void convertToLowerCase(Object obj) { + DomainTemplate template = (DomainTemplate) obj; + if (template != null) { + LIST.convertToLowerCase(template.getTemplateNames()); + } + } + }, + ENTITY { + void convertToLowerCase(Object obj) { + Entity entity = (Entity) obj; + entity.setName(entity.getName().toLowerCase()); + } + }, + LIST { + void convertToLowerCase(Object obj) { + @SuppressWarnings("unchecked") + List list = (List) obj; + if (list != null) { + ListIterator iter = list.listIterator(); + while (iter.hasNext()) { + iter.set(iter.next().toLowerCase()); + } + } + } + }, + MEMBERSHIP { + void convertToLowerCase(Object obj) { + Membership membership = (Membership) obj; + membership.setMemberName(membership.getMemberName().toLowerCase()); + if (membership.getRoleName() != null) { + membership.setRoleName(membership.getRoleName().toLowerCase()); + } + } + }, + POLICY { + void convertToLowerCase(Object obj) { + Policy policy = (Policy) obj; + policy.setName(policy.getName().toLowerCase()); + if (policy.getAssertions() != null) { + for (Assertion assertion : policy.getAssertions()) { + ASSERTION.convertToLowerCase(assertion); + } + } + } + }, + PROVIDER_RESOURCE_GROUP_ROLES { + void convertToLowerCase(Object obj) { + ProviderResourceGroupRoles tenantRoles = (ProviderResourceGroupRoles) obj; + tenantRoles.setDomain(tenantRoles.getDomain().toLowerCase()); + tenantRoles.setService(tenantRoles.getService().toLowerCase()); + tenantRoles.setTenant(tenantRoles.getTenant().toLowerCase()); + tenantRoles.setResourceGroup(tenantRoles.getResourceGroup().toLowerCase()); + if (tenantRoles.getRoles() != null) { + for (TenantRoleAction roleAction : tenantRoles.getRoles()) { + TENANT_ROLE_ACTION.convertToLowerCase(roleAction); + } + } + } + }, + PUBLIC_KEY_ENTRY { + void convertToLowerCase(Object obj) { + PublicKeyEntry keyEntry = (PublicKeyEntry) obj; + keyEntry.setId(keyEntry.getId().toLowerCase()); + } + }, + ROLE { + void convertToLowerCase(Object obj) { + Role role = (Role) obj; + role.setName(role.getName().toLowerCase()); + if (role.getTrust() != null) { + role.setTrust(role.getTrust().toLowerCase()); + } + LIST.convertToLowerCase(role.getMembers()); + } + }, + SERVICE_IDENTITY { + void convertToLowerCase(Object obj) { + ServiceIdentity service = (ServiceIdentity) obj; + service.setName(service.getName().toLowerCase()); + LIST.convertToLowerCase(service.getHosts()); + if (service.getPublicKeys() != null) { + for (PublicKeyEntry key : service.getPublicKeys()) { + PUBLIC_KEY_ENTRY.convertToLowerCase(key); + } + } + } + }, + SUB_DOMAIN { + void convertToLowerCase(Object obj) { + SubDomain subdomain = (SubDomain) obj; + subdomain.setName(subdomain.getName().toLowerCase()); + subdomain.setParent(subdomain.getParent().toLowerCase()); + LIST.convertToLowerCase(subdomain.getAdminUsers()); + DOMAIN_TEMPLATE_LIST.convertToLowerCase(subdomain.getTemplates()); + } + }, + TENANCY { + void convertToLowerCase(Object obj) { + Tenancy tenancy = (Tenancy) obj; + tenancy.setDomain(tenancy.getDomain().toLowerCase()); + tenancy.setService(tenancy.getService().toLowerCase()); + LIST.convertToLowerCase(tenancy.getResourceGroups()); + } + }, + TENANCY_RESOURCE_GROUP { + void convertToLowerCase(Object obj) { + TenancyResourceGroup tenancyResourceGroup = (TenancyResourceGroup) obj; + tenancyResourceGroup.setDomain(tenancyResourceGroup.getDomain().toLowerCase()); + tenancyResourceGroup.setService(tenancyResourceGroup.getService().toLowerCase()); + tenancyResourceGroup.setResourceGroup(tenancyResourceGroup.getResourceGroup().toLowerCase()); + } + }, + TENANT_RESOURCE_GROUP_ROLES { + void convertToLowerCase(Object obj) { + TenantResourceGroupRoles tenantRoles = (TenantResourceGroupRoles) obj; + tenantRoles.setDomain(tenantRoles.getDomain().toLowerCase()); + tenantRoles.setService(tenantRoles.getService().toLowerCase()); + tenantRoles.setTenant(tenantRoles.getTenant().toLowerCase()); + tenantRoles.setResourceGroup(tenantRoles.getResourceGroup().toLowerCase()); + if (tenantRoles.getRoles() != null) { + for (TenantRoleAction roleAction : tenantRoles.getRoles()) { + TENANT_ROLE_ACTION.convertToLowerCase(roleAction); + } + } + } + }, + TENANT_ROLE_ACTION { + void convertToLowerCase(Object obj) { + TenantRoleAction roleAction = (TenantRoleAction) obj; + roleAction.setAction(roleAction.getAction().toLowerCase()); + roleAction.setRole(roleAction.getRole().toLowerCase()); + } + }, + TENANT_ROLES { + void convertToLowerCase(Object obj) { + TenantRoles tenantRoles = (TenantRoles) obj; + tenantRoles.setDomain(tenantRoles.getDomain().toLowerCase()); + tenantRoles.setService(tenantRoles.getService().toLowerCase()); + tenantRoles.setTenant(tenantRoles.getTenant().toLowerCase()); + if (tenantRoles.getRoles() != null) { + for (TenantRoleAction roleAction : tenantRoles.getRoles()) { + TENANT_ROLE_ACTION.convertToLowerCase(roleAction); + } + } + } + }, + TOP_LEVEL_DOMAIN { + void convertToLowerCase(Object obj) { + TopLevelDomain domain = (TopLevelDomain) obj; + domain.setName(domain.getName().toLowerCase()); + LIST.convertToLowerCase(domain.getAdminUsers()); + DOMAIN_TEMPLATE_LIST.convertToLowerCase(domain.getTemplates()); + } + }, + USER_DOMAIN { + void convertToLowerCase(Object obj) { + UserDomain userDomain = (UserDomain) obj; + userDomain.setName(userDomain.getName().toLowerCase()); + DOMAIN_TEMPLATE_LIST.convertToLowerCase(userDomain.getTemplates()); + } + }; + + abstract void convertToLowerCase(Object obj); + } + + AuditLogger auditLogger = null; + static String auditLoggerMsgBldrClass = null; + + public ZMSImpl(String serverHostName, ObjectStore dbStore, Metric metric, + PrivateKey privateKey, String privateKeyId, String publicKey, + AuditLogger auditLog, String auditLogMsgBldrClass) { + + auditLogger = auditLog; + auditLoggerMsgBldrClass = auditLogMsgBldrClass; + + // first determine our root directory + + zmsRootDir = System.getenv("ROOT"); + if (zmsRootDir == null) { + zmsRootDir = "/home/athenz"; + } + + this.publicKey = publicKey; + this.privateKey = privateKey; + this.privateKeyId = privateKeyId; + this.schema = ZMSSchema.instance(); + validator = new Validator(schema); + userDomain = System.getProperty(ZMSConsts.ZMS_PROP_USER_DOMAIN, ZMSConsts.USER_DOMAIN); + userDomainPrefix = userDomain + "."; + + ZMSImpl.serverHostName = serverHostName; + ZMSImpl.metric = metric; + this.dbStore = dbStore; + dbService = new DBService(dbStore, auditLogger, userDomain); + userTokenTimeout = Integer.parseInt(System.getProperty(ZMSConsts.ZMS_PROP_TIMEOUT, "3600")); + + // check if we need to run in maintenance read only mode + + readOnlyMode = Boolean.parseBoolean(System.getProperty(ZMSConsts.ZMS_PROP_READ_ONLY_MODE, "false")); + + // check to see if we need to support product ids as required + // for top level domains + + productIdSupport = Boolean.parseBoolean(System.getProperty(ZMSConsts.ZMS_PROP_PRODUCT_ID_SUPPORT, "false")); + + // get the list of valid provider endpoints + + String endPoints = System.getProperty(ZMSConsts.ZMS_PROP_PROVIDER_ENDPOINTS); + if (endPoints != null) { + providerEndpoints = Arrays.asList(endPoints.split(",")); + } + // retrieve virtual domain support and limit. If we're given an invalid negative + // value for limit, we'll default back to our configured value of 2 + + virtualDomainSupport = Boolean.parseBoolean(System.getProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN, "true")); + virtualDomainLimit = Integer.parseInt(System.getProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT, "2")); + if (virtualDomainLimit < 0) { + virtualDomainLimit = 2; + } + + // signedPolicyTimeout is in milliseconds but the config setting should be in seconds + // to be consistent with other configuration properties (Default 7 days) + + signedPolicyTimeout = 1000 * Long.parseLong(System.getProperty(ZMSConsts.ZMS_PROP_SIGNED_POLICY_TIMEOUT, "604800")); + if (signedPolicyTimeout < 0) { + signedPolicyTimeout = 1000 * 604800; + } + + // get the ports the server is configured to listen on + + serverHttpsPort = System.getProperty(ZMSConsts.ZMS_PROP_HTTPS_PORT, Integer.toString(ZMSConsts.ZMS_HTTPS_PORT_DEFAULT)); + serverHttpPort = System.getProperty(ZMSConsts.ZMS_PROP_HTTP_PORT, Integer.toString(ZMSConsts.ZMS_HTTP_PORT_DEFAULT)); + + // get the maximum length allowed for a top level domain name + + domainNameMaxLen = Integer.parseInt(System.getProperty(ZMSConsts.ZMS_PROP_DOMAIN_NAME_MAX_SIZE, + ZMSConsts.ZMS_DOMAIN_NAME_MAX_SIZE_DEFAULT)); + if (domainNameMaxLen < 10) { // 10 is arbitrary + int domNameMaxDefault = Integer.parseInt(ZMSConsts.ZMS_DOMAIN_NAME_MAX_SIZE_DEFAULT); + LOG.warn("init: Warning: maximum domain name length specified is too small: " + + domainNameMaxLen + " : reverting to default: " + domNameMaxDefault); + domainNameMaxLen = domNameMaxDefault; + } + LOG.info("init: using maximum domain name length: " + domainNameMaxLen); + + // load the list of authorized services + + loadAuthorizedServices(); + + // load the Solution templates + + loadSolutionTemplates(); + + // this should only happen when running ZMS in local/debug mode + // otherwise the store should have been initialized by now + + initObjectStore(); + + // retrieve our public keys + + loadServerPublicKeys(); + } + + void loadServerPublicKeys() { + + // initialize our public key map + + serverPublicKeyMap = new ConcurrentHashMap(); + + // retrieve our zms service identity object + + ServiceIdentity identity = dbService.getServiceIdentity(SYS_AUTH, ZMS); + if (identity != null) { + + // process all the public keys and add them to the map + + List publicKeyList = identity.getPublicKeys(); + if (publicKeyList != null) { + for (PublicKeyEntry entry : publicKeyList) { + serverPublicKeyMap.put(entry.getId(), entry.getKey()); + } + } + } + + // this should never happen but just in case we'll just + // use the public key we retrieved ourselves to the map + + if (serverPublicKeyMap.isEmpty()) { + serverPublicKeyMap.put(privateKeyId, publicKey); + } + } + + public void setProviderClientClass(Class providerClass) { + this.providerClass = providerClass; + } + + public void putAuthorityList(Http.AuthorityList authList) { + authorities = authList; + } + + void loadSolutionTemplates() { + + // get the configured path for the list of service templates + + String solutionTemplatesFname = System.getProperty(ZMSConsts.ZMS_PROP_SOLUTION_TEMPLATE_FNAME, + zmsRootDir + "/conf/zms_server/solution_templates.json"); + + Path path = Paths.get(solutionTemplatesFname); + try { + serverSolutionTemplates = JSON.fromBytes(Files.readAllBytes(path), SolutionTemplates.class); + } catch (IOException e) { + LOG.info("Unable to parse service templates file " + solutionTemplatesFname); + return; + } + + if (serverSolutionTemplates == null) { + LOG.info("Unable to parse service templates file " + solutionTemplatesFname); + return; + } + } + + void loadAuthorizedServices() { + + // get the configured path for the list of authorized services and what operations + // those services are allowed to process + + String authzServiceFname = System.getProperty(ZMSConsts.ZMS_PROP_AUTHZ_SERVICE_FNAME, + zmsRootDir + "/conf/zms_server/authorized_services.json"); + + // let's read our authorized list into a local struct + + File file = new File(authzServiceFname); + if (file.exists() == false) { + LOG.info("Authorized Service File " + authzServiceFname + " not found"); + return; + } + + Path path = Paths.get(file.toURI()); + try { + serverAuthorizedServices = JSON.fromBytes(Files.readAllBytes(path), AuthorizedServices.class); + } catch (IOException e) { + LOG.info("Unable to parse authorized service file " + authzServiceFname); + } + } + + void initObjectStore() { + + final String caller = "initstore"; + + List domains = dbService.listDomains(null, 0); + if (domains.size() > 0) { + return; + } + + String adminUserList = System.getProperty(ZMSConsts.ZMS_PROP_DOMAIN_ADMIN); + if (adminUserList == null) { + throw ZMSUtils.internalServerError("init: No ZMS admin user specified", caller); + } + + String[] users = adminUserList.split(","); + ArrayList adminUsers = new ArrayList(); + for (int i = 0; i < users.length; i++) { + String adminUser = users[i].trim(); + if (!adminUser.startsWith(userDomainPrefix)) { + throw ZMSUtils.internalServerError("init: Bad domain user name(" + adminUser + + "), must begin with (" + userDomainPrefix + ")", caller); + } + adminUsers.add(adminUser); + } + + if (!ZMSConsts.USER_DOMAIN.equals(userDomain)) { + createTopLevelDomain(null, userDomain, "The reserved domain for user authentication", + null, null, adminUsers, null, 0, null, null); + } + createTopLevelDomain(null, userDomain, "The reserved domain for user authentication", + null, null, adminUsers, null, 0, null, null); + createTopLevelDomain(null, "sys", "The reserved domain for system related information", + null, null, adminUsers, null, 0, null, null); + createSubDomain(null, "sys", "auth", "The AuthNG domain", null, null, adminUsers, + null, 0, null, null, caller); + + if (publicKey != null) { + List pubKeys = new ArrayList<>(); + pubKeys.add(new PublicKeyEntry().setId(privateKeyId).setKey(publicKey)); + ServiceIdentity id = new ServiceIdentity().setName("sys.auth.zms").setPublicKeys(pubKeys); + dbService.executePutServiceIdentity(null, SYS_AUTH, ZMS, id, null, caller); + } else { + if (LOG.isWarnEnabled()) { + LOG.warn("init: Warning: no public key, cannot register sys.auth.zms identity"); + } + } + } + + /** + * @return the ZMS Schema object, describing its API and types. + */ + public Schema schema() { + return schema; + } + + /** + * Setup a new AuditLogMsgBuilder object with common values. + **/ + static AuditLogMsgBuilder getAuditLogMsgBuilder(ResourceContext ctx, String domainName, String auditRef, + String caller, String method) { + + AuditLogMsgBuilder msgBldr = null; + try { + msgBldr = AuditLogFactory.getMsgBuilder(auditLoggerMsgBldrClass); + } catch (Exception exc) { + LOG.error("getAuditLogMsgBuilder: failed to get an AuditLogMsgBuilder. Get the default instead.", exc); + msgBldr = AuditLogFactory.getMsgBuilder(); + } + + // get the where - which means where this server is running + + msgBldr.whereIp(serverHostName).whereHttpsPort(serverHttpsPort).whereHttpPort(serverHttpPort); + msgBldr.whatDomain(domainName).why(auditRef).whatApi(caller).whatMethod(method); + + // get the 'who' and set it + + if (ctx != null) { + Principal princ = ((RsrcCtxWrapper) ctx).principal(); + if (princ != null) { + String unsignedCreds = princ.getUnsignedCredentials(); + if (unsignedCreds == null) { + StringBuilder sb = new StringBuilder(); + sb.append("who-name=").append(princ.getName()); + sb.append(",who-domain=").append(princ.getDomain()); + sb.append(",who-yrn=").append(princ.getYRN()); + List roles = princ.getRoles(); + if (roles != null && roles.size() > 0) { + sb.append(",who-roles=").append(roles.toString()); + } + unsignedCreds = sb.toString(); + } + msgBldr.who(unsignedCreds); + } + + // get the client IP + + msgBldr.clientIp(ServletRequestUtil.getRemoteAddress(ctx.request())); + } + + return msgBldr; + } + + // ----------------- the Domain interface { + + public DomainList getDomainList(ResourceContext ctx, Integer limit, String skip, String prefix, + Integer depth, String account, Integer productId, String roleMember, String roleName, + String modifiedSince) { + + final String caller = "getdomainlist"; + metric.increment(ZMSConsts.HTTP_GET); + metric.increment(ZMSConsts.HTTP_REQUEST); + metric.increment(caller); + Object timerMetric = metric.startTiming("getdomainlist_timing", null); + + if (LOG.isDebugEnabled()) { + LOG.debug("getDomainList: limit: " + limit + " skip: " + skip + + " prefix: " + prefix + " depth: " + depth + " modifiedSince: " + modifiedSince); + } + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + if (skip != null) { + skip = skip.toLowerCase(); + } + if (prefix != null) { + prefix = prefix.toLowerCase(); + } + if (roleMember != null) { + roleMember = roleMember.toLowerCase(); + validate(roleMember, TYPE_ENTITY_NAME, caller); + } + if (roleName != null) { + roleName = roleName.toLowerCase(); + validate(roleName, TYPE_ENTITY_NAME, caller); + } + if (limit != null && limit <= 0) { + throw ZMSUtils.requestError("getDomainList: limit must be positive: " + limit, caller); + } + + long modTime = 0; + if (modifiedSince != null) { + // we only support RFC1123 format for if-modified-since format + + SimpleDateFormat dateFmt = new SimpleDateFormat(ZMSConsts.HTTP_RFC1123_DATE_FORMAT); + dateFmt.setTimeZone(TimeZone.getTimeZone(ZMSConsts.HTTP_DATE_GMT_ZONE)); + try { + Date date = dateFmt.parse(modifiedSince); + modTime = date.getTime(); + } catch (ParseException ex) { + throw ZMSUtils.requestError("getDomainList: If-Modified-Since header value must be valid RFC1123 date" + + ex.getMessage(), caller); + } + } + + // if we have account specified then we're going to ignore all + // other fields since there should only be one domain that + // matches the specified account. Otherwise, we're going to do + // the same thing for product id since there should also be one + // domain with that id. If neither one is present, then we'll + // do our regular domain list + + DomainList dlist = null; + if (account != null && !account.isEmpty()) { + dlist = dbService.lookupDomainByAccount(account); + } else if (productId != null && productId.intValue() != 0) { + dlist = dbService.lookupDomainByProductId(productId); + } else if (roleMember != null || roleName != null) { + dlist = dbService.lookupDomainByRole(roleMember, roleName); + } else { + dlist = listDomains(limit, skip, prefix, depth, modTime); + } + + metric.stopTiming(timerMetric); + return dlist; + } + + public Domain getDomain(ResourceContext ctx, String domainName) { + + final String caller = "getdomain"; + metric.increment(ZMSConsts.HTTP_GET); + + + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + + Object timerMetric = metric.startTiming("getdomain_timing", domainName); + + Domain domain = dbService.getDomain(domainName); + if (domain == null) { + throw ZMSUtils.notFoundError("getDomain: Domain not found: " + domainName, caller); + } + + metric.stopTiming(timerMetric); + return domain; + } + + public Domain postTopLevelDomain(ResourceContext ctx, String auditRef, TopLevelDomain detail) { + + final String caller = "posttopleveldomain"; + metric.increment(ZMSConsts.HTTP_POST); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + Domain domain = null; + try { + validate(detail, TYPE_TOP_LEVEL_DOMAIN, caller); + + String domainName = detail.getName(); + validate(domainName, TYPE_DOMAIN_NAME, caller); + + domainName = domainName.toLowerCase(); + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("posttopleveldomain_timing", domainName); + + if (domainName.indexOf('_') != -1 && !isSysAdminUser(((RsrcCtxWrapper) ctx).principal())) { + throw ZMSUtils.requestError("Domain name cannot contain underscores", caller); + } + + // verify length of domain name + + if (domainName.length() > domainNameMaxLen) { + throw ZMSUtils.requestError("Invalid Domain name: " + domainName + + " : name length cannot exceed: " + domainNameMaxLen, caller); + } + + // verify that request is properly authenticated for this request + + Principal principal = ((RsrcCtxWrapper) ctx).principal(); + verifyAuthorizedServiceOperation(principal.getAuthorizedService(), caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + AthenzObject.TOP_LEVEL_DOMAIN.convertToLowerCase(detail); + + List solutionTemplates = null; + DomainTemplateList templates = detail.getTemplates(); + if (templates != null) { + solutionTemplates = templates.getTemplateNames(); + validateSolutionTemplates(solutionTemplates, caller); + } + + // check to see if we need to validate our product id for the top + // level domains. The server code assumes that product id with + // 0 indicates no enforcement + + int productId = 0; + if (productIdSupport) { + if (detail.getYpmId() != null) { + if ((productId = detail.getYpmId().intValue()) <= 0) { + throw ZMSUtils.requestError("Product Id must be a positive integer", caller); + } + } else { + throw ZMSUtils.requestError("Product Id is required when creating top level domain", caller); + } + } + + domain = createTopLevelDomain(ctx, domainName, detail.getDescription(), + detail.getOrg(), detail.getAuditEnabled(), detail.getAdminUsers(), + detail.getAccount(), productId, solutionTemplates, auditRef); + + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + String domainName = (detail != null) ? detail.getName() : null; + auditRequestFailure(ctx, exc, domainName, domainName, caller, + ZMSConsts.HTTP_POST, null, auditRef); + throw exc; + } + + return domain; + } + + public TopLevelDomain deleteTopLevelDomain(ResourceContext ctx, String domainName, String auditRef) { + + final String caller = "deletetopleveldomain"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + domainName = domainName.toLowerCase(); + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("deletetopleveldomain_timing", domainName); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + deleteDomain(ctx, auditRef, domainName, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, domainName, caller, ZMSConsts.HTTP_DELETE, null, auditRef); + throw exc; + } + + return null; + } + + Domain deleteDomain(ResourceContext ctx, String auditRef, String domainName, String caller) { + + DomainList subDomainList = listDomains(null, null, domainName + ".", null, 0); + if (subDomainList.getNames().size() > 0) { + throw ZMSUtils.requestError(caller + ": Cannot delete domain " + + domainName + ": " + subDomainList.getNames().size() + " subdomains of it exist", caller); + } + + Domain domain = dbService.executeDeleteDomain(ctx, domainName, auditRef, caller); + return domain; + } + + boolean isVirtualDomain(String domain) { + + // all virtual domains start with our user domain + + return domain.startsWith(userDomainPrefix); + } + + boolean hasExceededVirtualSubDomainLimit(String domain) { + + // we need to find our username which is our second + // component in the domain name - e.g. user.joe[.subdomain] + // when counting we need to make to include the trailing . + // since we're counting subdomains and we need to make sure + // not to match other users who have the same prefix + + String userDomain = null; + int idx = domain.indexOf(".", userDomainPrefix.length()); + if (idx == -1) { + userDomain = domain + "."; + } else { + userDomain = domain.substring(0, idx + 1); + } + + // retrieve the number of domains with this prefix + + DomainList dlist = listDomains(null, null, userDomain, null, 0); + if (dlist.getNames().size() < virtualDomainLimit) { + return false; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("hasExceededVirtualSubDomainLimit: subdomains with prefix " + userDomain + + ": " + dlist.getNames().size() + " while limit is: " + virtualDomainLimit); + } + + return true; + } + + public Domain postUserDomain(ResourceContext ctx, String name, String auditRef, UserDomain detail) { + + final String caller = "postuserdomain"; + metric.increment(ZMSConsts.HTTP_POST); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + validate(detail, TYPE_USER_DOMAIN, caller); + validate(name, TYPE_SIMPLE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + name = name.toLowerCase(); + AthenzObject.USER_DOMAIN.convertToLowerCase(detail); + + metric.increment(ZMSConsts.HTTP_REQUEST, name); + metric.increment(caller, name); + Object timerMetric = metric.startTiming("postuserdomain_timing", name); + + if (detail.getName().indexOf('_') != -1 && !isSysAdminUser(((RsrcCtxWrapper) ctx).principal())) { + throw ZMSUtils.requestError("Domain name cannot contain underscores", caller); + } + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + if (!name.equals(detail.getName())) { + throw ZMSUtils.forbiddenError("postUserDomain: Request and detail domain names do not match", caller); + } + + // we're dealing with user's top level domain so the parent is going + // to be the user domain and the admin of the domain is the user + + List adminUsers = new ArrayList<>(); + adminUsers.add(userDomainPrefix + name); + + List solutionTemplates = null; + DomainTemplateList templates = detail.getTemplates(); + if (templates != null) { + solutionTemplates = templates.getTemplateNames(); + validateSolutionTemplates(solutionTemplates, caller); + } + + Domain domain = createSubDomain(ctx, userDomain, detail.getName(), detail.getDescription(), + detail.getOrg(), detail.getAuditEnabled(), adminUsers, detail.getAccount(), 0, + solutionTemplates, auditRef, caller); + + metric.stopTiming(timerMetric); + return domain; + } + + public Domain postSubDomain(ResourceContext ctx, String parent, String auditRef, SubDomain detail) { + + final String caller = "postsubdomain"; + metric.increment(ZMSConsts.HTTP_POST); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + validate(detail, TYPE_SUB_DOMAIN, caller); + validate(parent, TYPE_DOMAIN_NAME, caller); + validate(detail.getName(), TYPE_SIMPLE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + parent = parent.toLowerCase(); + AthenzObject.SUB_DOMAIN.convertToLowerCase(detail); + + metric.increment(ZMSConsts.HTTP_REQUEST, parent); + metric.increment(caller, parent); + Object timerMetric = metric.startTiming("postsubdomain_timing", parent); + + if (detail.getName().indexOf('_') != -1 && !isSysAdminUser(((RsrcCtxWrapper) ctx).principal())) { + throw ZMSUtils.requestError("Domain name cannot contain underscores", caller); + } + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + if (!parent.equals(detail.getParent())) { + throw ZMSUtils.forbiddenError("postSubDomain: Request and detail parent domains do not match", caller); + } + + // if we're dealing with virtual/home domains (in the user's own namespace) + // and we don't have unlimited support for virtual domains then we need to + // make sure we don't exceed our configured number of virtual subdomains + // allowed per user + + if (virtualDomainLimit != 0 && isVirtualDomain(parent) && hasExceededVirtualSubDomainLimit(parent)) { + throw ZMSUtils.forbiddenError("postSubDomain: Exceeding the configured number of virtual subdomains", caller); + } + + List solutionTemplates = null; + DomainTemplateList templates = detail.getTemplates(); + if (templates != null) { + solutionTemplates = templates.getTemplateNames(); + validateSolutionTemplates(solutionTemplates, caller); + } + + // while it's not required for sub domains to have product ids + // we're going to store it in case there is a requirement to + // generate reports based on product ids even for subdomains + // unlike top level domains, passing 0 is ok here as it indicates + // that there is no product id + + int productId = 0; + if (productIdSupport) { + if (detail.getYpmId() != null) { + if ((productId = detail.getYpmId().intValue()) < 0) { + throw ZMSUtils.requestError("Product Id must be a positive integer", caller); + } + } + } + + Domain domain = createSubDomain(ctx, detail.getParent(), detail.getName(), detail.getDescription(), + detail.getOrg(), detail.getAuditEnabled(), detail.getAdminUsers(), detail.getAccount(), + productId, solutionTemplates, auditRef, caller); + + metric.stopTiming(timerMetric); + return domain; + } + + boolean isSysAdminUser(Principal principal) { + + // verify we're dealing with system administrator + // authorize ("CREATE", "sys.auth:domain"); + + AthenzDomain domain = getAthenzDomain(SYS_AUTH, true); + if (domain == null) { + return false; + } + + // evaluate our domain's roles and policies to see if access + // is allowed or not for the given operation and resource + // our action are always converted to lowercase + + String resource = SYS_AUTH + ":domain"; + AccessStatus accessStatus = evaluateAccess(domain, principal.getYRN(), "create", + resource, null, null); + + if (accessStatus == AccessStatus.ALLOWED) { + return true; + } else { + return false; + } + } + + boolean isAllowedResourceLookForAllUsers(Principal principal) { + + // the authorization policy resides in offical sys.auth domain + + AthenzDomain domain = getAthenzDomain(SYS_AUTH, true); + if (domain == null) { + return false; + } + + // evaluate our domain's roles and policies to see if access + // is allowed or not for the given operation and resource + // our action are always converted to lowercase + + String resource = SYS_AUTH + ":resource-lookup-all"; + AccessStatus accessStatus = evaluateAccess(domain, principal.getYRN(), "access", + resource, null, null); + + if (accessStatus == AccessStatus.ALLOWED) { + return true; + } else { + return false; + } + } + + public SubDomain deleteSubDomain(ResourceContext ctx, String parent, String name, String auditRef) { + + final String caller = "deletesubdomain"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + parent = parent.toLowerCase(); + name = name.toLowerCase(); + String domainName = parent + "." + name; + + metric.increment(ZMSConsts.HTTP_REQUEST, parent); + metric.increment(caller, parent); + Object timerMetric = metric.startTiming("deletesubdomain_timing", parent); + + try { + validate(parent, TYPE_DOMAIN_NAME, caller); + validate(name, TYPE_SIMPLE_NAME, caller); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + deleteDomain(ctx, auditRef, domainName, caller); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, domainName, caller, ZMSConsts.HTTP_DELETE, null, auditRef); + throw exc; + } + + metric.stopTiming(timerMetric); + return null; + } + + public UserDomain deleteUserDomain(ResourceContext ctx, String name, String auditRef) { + + final String caller = "deleteuserdomain"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(name, TYPE_SIMPLE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + name = name.toLowerCase(); + metric.increment(ZMSConsts.HTTP_REQUEST, name); + metric.increment(caller, name); + Object timerMetric = metric.startTiming("deleteuserdomain_timing", name); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + String domainName = userDomainPrefix + name; + deleteDomain(ctx, auditRef, domainName, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, name, name, caller, ZMSConsts.HTTP_DELETE, null, auditRef); + throw exc; + } + + return null; + } + + public Domain putDomainMeta(ResourceContext ctx, String domainName, String auditRef, DomainMeta meta) { + + final String caller = "putdomainmeta"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(meta, TYPE_DOMAIN_META, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("putdomainmeta_timing", domainName); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + if (LOG.isDebugEnabled()) { + LOG.debug("putDomainMeta: name=" + domainName + ", meta=" + meta); + } + + // for top level domains, verify the meta data has a product id and that it is not used by + // any other domain(other than this one) + + if (productIdSupport && domainName.indexOf('.') == -1) { + + // if this productId is already used by any domain it will be + // seen in dbService and exception thrown + + Integer productId = meta.getYpmId(); + if (productId == null) { + throw ZMSUtils.requestError("Unique Product Id must be specified for top level domain", caller); + } + } + + dbService.executePutDomainMeta(ctx, domainName, meta, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, domainName, caller, ZMSConsts.HTTP_PUT, null, auditRef); + throw exc; + } + + return null; + } + + void validateSolutionTemplates(List templateNames, String caller) { + for (String templateName : templateNames) { + if (!serverSolutionTemplates.contains(templateName)) { + throw ZMSUtils.notFoundError("validateSolutionTemplates: Template not found: " + templateName, caller); + } + } + } + + public DomainTemplateList getDomainTemplateList(ResourceContext ctx, String domainName) { + + final String caller = "getdomaintemplatelist"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getdomaintemplatelist_timing", domainName); + + DomainTemplateList domainTemplateList = dbService.listDomainTemplates(domainName); + if (domainTemplateList == null) { + throw ZMSUtils.notFoundError("getDomainTemplateList: Domain not found: '" + domainName + "'", caller); + } + + metric.stopTiming(timerMetric); + return domainTemplateList; + } + + public DomainTemplate putDomainTemplate(ResourceContext ctx, String domainName, String auditRef, + DomainTemplate templates) { + + final String caller = "putdomaintemplate"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(templates, TYPE_DOMAIN_TEMPLATE, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + AthenzObject.DOMAIN_TEMPLATE.convertToLowerCase(templates); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("putdomaintemplate_timing", domainName); + + // verify that all template names are valid + + List templateNames = templates.getTemplateNames(); + if (templateNames == null || templateNames.size() == 0) { + throw ZMSUtils.requestError("putDomainTemplate: No templates specified", caller); + } + validateSolutionTemplates(templateNames, caller); + + // verify that request is properly authenticated for this request + // Make sure each template name is verified + + for (String templateName : templates.getTemplateNames()) { + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), + caller, "name", templateName); + } + + dbService.executePutDomainTemplate(ctx, domainName, templateNames, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + String auditDetails = auditListItems("caller specified templates", templates.getTemplateNames()); + auditRequestFailure(ctx, exc, domainName, domainName, caller, ZMSConsts.HTTP_PUT, + auditDetails, auditRef); + throw exc; + } + + return null; + } + + public DomainTemplate deleteDomainTemplate(ResourceContext ctx, String domainName, String templateName, String auditRef) { + + final String caller = "deletedomaintemplate"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("deletedomaintemplate_timing", domainName); + + if (LOG.isDebugEnabled()) { + LOG.debug("deleteDomainTemplate: domain=" + domainName + ", template=" + templateName); + } + + // verify the template name is valid + + if (templateName == null || templateName.length() == 0) { + throw ZMSUtils.requestError("deleteDomainTemplate: No template specified", caller); + } + + templateName = templateName.toLowerCase(); + List templateNames = new ArrayList(); + templateNames.add(templateName); + validateSolutionTemplates(templateNames, caller); + + dbService.executeDeleteDomainTemplate(ctx, domainName, templateName, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified templatename=(").append(templateName).append(")"); + auditRequestFailure(ctx, exc, domainName, domainName, caller, ZMSConsts.HTTP_DELETE, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + // ----------------- end of the Domain interface } + + Principal createPrincipalForName(String principalName) { + + String domain = null; + String name = null; + + /* if we have no . in the principal name we're going to default to standard user */ + + int idx = principalName.lastIndexOf('.'); + if (idx == -1) { + domain = userDomain; + name = principalName; + } else { + domain = principalName.substring(0, idx); + name = principalName.substring(idx + 1); + } + + return SimplePrincipal.create(domain, name, (String) null); + } + + boolean validRoleTokenAccess(String trustDomain, String domainYRN, String principalYRN) { + + if (trustDomain != null) { + if (LOG.isWarnEnabled()) { + LOG.warn("validRoleTokenAccess: Cannot access cross-domain resources with RoleToken"); + } + return false; + } + + // for Role tokens we don't have a name component in the principal + // so the principalYRN should be the same as the domain value + // thus it must match the domainYRN from the resource + + if (!domainYRN.equalsIgnoreCase(principalYRN)) { + if (LOG.isWarnEnabled()) { + LOG.warn("validRoleTokenAccess: resource domain YRN does not match RoleToken domain"); + } + return false; + } + + return true; + } + + AthenzDomain getAthenzDomain(String domainName, boolean ignoreExceptions) { + + AthenzDomain domain = null; + try { + domain = dbService.getAthenzDomain(domainName); + } catch (ResourceException ex) { + + if (LOG.isDebugEnabled()) { + LOG.debug("getAthenzDomain failure: " + ex.getMessage()); + } + + if (!ignoreExceptions) { + if (ex.getCode() != ResourceException.NOT_FOUND) { + throw ex; + } + } + } + return domain; + } + + AthenzDomain retrieveAccessDomain(String domainName, Principal principal) { + + if (LOG.isDebugEnabled()) { + LOG.debug("retrieveAccessDomain: identityYRN: " + principal.getYRN() + + " domain: " + domainName); + } + + AthenzDomain domain = getAthenzDomain(domainName, false); + if (domain != null) { + return domain; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("retrieveAccessDomain: domain not found, looking for virtual domain"); + } + + // if we don't have virtual/home domains enabled then no need + // to continue further + + if (!virtualDomainSupport) { + return null; + } + + if (principal.getDomain() == null) { + return null; + } + + if (!principal.getYRN().equals(domainName)) { + return null; + } + + return virtualHomeDomain(principal); + } + + AccessStatus evaluateAccess(AthenzDomain domain, String identityYRN, String action, String resource, + List authenticatedRoles, String trustDomain) { + + AccessStatus accessStatus = AccessStatus.DENIED; + + List policies = domain.getPolicies(); + List roles = domain.getRoles(); + + for (Policy policy : policies) { + + if (LOG.isDebugEnabled()) { + LOG.debug("evaluateAccess: processing policy: " + policy.getName()); + } + + // we are going to process all the assertions defined in this + // policy. As soon as we get a match for an assertion that + // denies access, we're going to return that result. If we + // get a match for an assertion that allows access we're + // going to remember that result and continue looking at + // all the assertions in case there is something else that + // explicitly denies access + + List assertions = policy.getAssertions(); + if (assertions == null) { + continue; + } + + for (Assertion assertion : assertions) { + + // get the effect for the assertion which is set + // as allowed by default + + AssertionEffect effect = assertion.getEffect(); + if (effect == null) { + effect = AssertionEffect.ALLOW; + } + + // if we have already matched an allow assertion then + // we'll automatically skip any assertion that has + // allow effect since there is no point of matching it + + if (accessStatus == AccessStatus.ALLOWED && effect == AssertionEffect.ALLOW) { + continue; + } + + // if no match then process the next assertion + + if (!assertionMatch(assertion, identityYRN, action, resource, roles, authenticatedRoles, trustDomain)) { + continue; + } + + // if the assertion has matched and the effect is deny + // then we're going to return right away otherwise we'll + // set our return allow matched flag to true and continue + // processing other assertions + + if (effect == AssertionEffect.DENY) { + return AccessStatus.DENIED; + } + + accessStatus = AccessStatus.ALLOWED; + } + } + + return accessStatus; + } + + public boolean access(String action, String resource, Principal principal, String trustDomain) { + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + resource = resource.toLowerCase(); + if (trustDomain != null) { + trustDomain = trustDomain.toLowerCase(); + } + action = action.toLowerCase(); + + // if the resource starts with the user domain and the environment is using + // a different domain name we'll dynamically update the resource value + + if (!userDomain.equals(ZMSConsts.USER_DOMAIN) && resource.startsWith(ZMSConsts.USER_DOMAIN_PREFIX)) { + resource = userDomain + resource.substring(ZMSConsts.USER_DOMAIN_PREFIX.length()); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("access:(" + action + ", " + resource + ", " + principal + ", " + trustDomain + ")"); + } + + // check to see if the authority is allowed to be processed in + // authorization checks. If this value is false then the principal + // must get a usertoken from ZMS first and the submit the request + // with that token + + if (!authorityAuthorizationAllowed(principal)) { + LOG.error("Authority is not allowed to support authorization checks"); + return false; + } + + // retrieve our domain based on resource and action/trustDomain pair + // we want to provider better error reporting to the users so if we get a + // request where the domain is not found instead of just returning 403 + // forbidden (which is confusing since it assumes the user doesn't have + // access as oppose to possible mistype of the domain name by the user) + // we want to return 404 not found. The athenz server common has special handling + // for rest.ResourceExceptions so we'll throw that exception in this + // special case of not found domains. + + String domainName = retrieveResourceDomain(resource, action, trustDomain); + if (domainName == null) { + throw new com.yahoo.athenz.common.server.rest.ResourceException(ResourceException.NOT_FOUND, "Domain not found"); + } + AthenzDomain domain = retrieveAccessDomain(domainName, principal); + if (domain == null) { + throw new com.yahoo.athenz.common.server.rest.ResourceException(ResourceException.NOT_FOUND, "Domain not found"); + } + + AccessStatus accessStatus = hasAccess(domain, domainName, action, resource, principal, trustDomain); + if (accessStatus == AccessStatus.ALLOWED) { + return true; + } + + return false; + } + + boolean authorityAuthorizationAllowed(Principal principal) { + + Authority authority = principal.getAuthority(); + if (authority == null) { + return true; + } + + return authority.allowAuthorization(); + } + + String retrieveResourceDomain(String resource, String op, String trustDomain) { + + // special handling for ASSUME_ROLE assertions. Since any assertion with + // that action refers to a resource in another domain, there is no point + // to retrieve the domain name from the resource. In these cases the caller + // must specify the trust domain attribute so we'll use that instead and + // if one is not specified then we'll fall back to using the domain name + // from the resource + + String domainName = null; + if (ZMSConsts.ACTION_ASSUME_ROLE.equalsIgnoreCase(op) && trustDomain != null) { + domainName = trustDomain; + } else { + domainName = yrnDomain(resource); + } + return domainName; + } + + AccessStatus hasAccess(AthenzDomain domain, String domainName, String action, String resource, + Principal principal, String trustDomain) { + + String identityYRN = principal.getYRN(); + + // if we're dealing with an access check based on a Role token then + // make sure it's valid before processing it + + List authenticatedRoles = principal.getRoles(); + if (authenticatedRoles != null && !validRoleTokenAccess(trustDomain, domainName, identityYRN)) { + return AccessStatus.DENIED_INVALID_ROLE_TOKEN; + } + + // evaluate our domain's roles and policies to see if access + // is allowed or not for the given operation and resource + + return evaluateAccess(domain, identityYRN, action, resource, authenticatedRoles, trustDomain); + } + + public Access getAccessExt(ResourceContext ctx, String action, String resource, String trustDomain, + String checkPrincipal) { + + // for now we'll just fall back to our constraind version + + return getAccess(ctx, action, resource, trustDomain, checkPrincipal); + } + + public Access getAccess(ResourceContext ctx, String action, String resource, String trustDomain, + String checkPrincipal) { + + final String caller = "getaccess"; + metric.increment(ZMSConsts.HTTP_GET); + + Principal principal = ((RsrcCtxWrapper) ctx).principal(); + if (LOG.isDebugEnabled()) { + LOG.debug("getAccess:(" + action + ", " + resource + ", " + principal + + ", " + trustDomain + ", " + checkPrincipal + ")"); + } + + validate(action, TYPE_COMPOUND_NAME, caller); + validate(resource, TYPE_YRN, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + action = action.toLowerCase(); + resource = resource.toLowerCase(); + if (checkPrincipal != null) { + checkPrincipal = checkPrincipal.toLowerCase(); + } + if (trustDomain != null) { + trustDomain = trustDomain.toLowerCase(); + } + + // retrieve the domain based on our resource and action/trustDomain pair + + String domainName = retrieveResourceDomain(resource, action, trustDomain); + if (domainName == null) { + metric.increment(ZMSConsts.HTTP_REQUEST, ZMSConsts.ZMS_INVALID_DOMAIN); + metric.increment(caller, ZMSConsts.ZMS_INVALID_DOMAIN); + throw ZMSUtils.notFoundError("getAccess: Unable to extract resource domain", caller); + } + AthenzDomain domain = retrieveAccessDomain(domainName, principal); + if (domain == null) { + metric.increment(ZMSConsts.HTTP_REQUEST, ZMSConsts.ZMS_UNKNOWN_DOMAIN); + metric.increment(caller, ZMSConsts.ZMS_UNKNOWN_DOMAIN); + throw ZMSUtils.notFoundError("getAccess: Resource Domain not found: '" + domainName + "'", caller); + } + + // start our counter with domain dimension. we're moving the metric here + // after the domain name has been confirmed as valid since with + // dimensions we get stuck with persistent indexes so we only want + // to create them for valid domain names + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getaccess_timing", domainName); + + /* if the check principal is given then we need to carry out the access + * check against that principal */ + + if (checkPrincipal != null) { + principal = createPrincipalForName(checkPrincipal); + if (principal == null) { + throw ZMSUtils.unauthorizedError("getAccess: Invalid check principal value specified", caller); + } + } + + boolean accessAllowed = false; + AccessStatus accessStatus = hasAccess(domain, domainName, action, resource, principal, trustDomain); + if (accessStatus == AccessStatus.ALLOWED) { + accessAllowed = true; + } + Access access = new Access().setGranted(accessAllowed); + + metric.stopTiming(timerMetric); + return access; + } + + // ----------------- the Entity interface + + boolean equalToOrPrefixedBy(String pattern, String name) { + if (name.equals(pattern)) { + return true; + } + if (name.startsWith(pattern + ".")) { + return true; + } + return false; + } + + void validateEntity(String entityName, Entity entity) { + + final String caller = "validateentity"; + + if (!entityName.equals(entity.getName())) { + throw ZMSUtils.requestError("validateEntity: Entity name mismatch: " + entityName + " != " + entity.getName(), caller); + } + if (entity.getValue() == null) { + throw ZMSUtils.requestError("validateEntity: Entity value is empty: " + entityName, caller); + } + } + + void checkReservedEntityName(String en) { + + final String caller = "checkreservedentityname"; + + final String [] reservedList = {META_FIELD, DOMAIN_FIELD, ROLE_FIELD, POLICY_FIELD, SERVICE_FIELD, TEMPLATE_FIELD}; + for (String reservedName : reservedList) { + if (equalToOrPrefixedBy(reservedName, en)) { + throw ZMSUtils.requestError("checkReservedEntityName: Bad entity name: reserved name or prefix: " + en, caller); + } + } + } + + public Entity putEntity(ResourceContext ctx, String domainName, String entityName, String auditRef, Entity resource) { + + final String caller = "putentity"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(entityName, TYPE_ENTITY_NAME, caller); + checkReservedEntityName(entityName); + validateEntity(entityName, resource); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + entityName = entityName.toLowerCase(); + AthenzObject.ENTITY.convertToLowerCase(resource); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("putentity_timing", domainName); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + dbService.executePutEntity(ctx, domainName, entityName, resource, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, entityName, caller, ZMSConsts.HTTP_PUT, null, auditRef); + throw exc; + } + + return null; + } + + @Override + public EntityList getEntityList(ResourceContext ctx, String domainName) { + + final String caller = "getentitylist"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getentitylist_timing", domainName); + + EntityList result = new EntityList(); + List names = dbService.listEntities(domainName); + result.setNames(names); + + metric.stopTiming(timerMetric); + return result; + } + + public Entity getEntity(ResourceContext ctx, String domainName, String entityName) { + + final String caller = "getentity"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(entityName, TYPE_ENTITY_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + entityName = entityName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getentity_timing", null); + + Entity entity = dbService.getEntity(domainName, entityName); + if (entity == null) { + throw ZMSUtils.notFoundError("getEntity: Entity not found: '" + + ZMSUtils.entityResourceName(domainName, entityName) + "'", caller); + } + + metric.stopTiming(timerMetric); + return entity; + } + + public Entity deleteEntity(ResourceContext ctx, String domainName, String entityName, String auditRef) { + + final String caller = "deleteentity"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(entityName, TYPE_ENTITY_NAME, caller); + checkReservedEntityName(entityName); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + entityName = entityName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("deleteentity_timing", null); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + dbService.executeDeleteEntity(ctx, domainName, entityName, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, entityName, caller, ZMSConsts.HTTP_DELETE, null, auditRef); + throw exc; + } + return null; + } + + public ServerTemplateList getServerTemplateList(ResourceContext ctx) { + + final String caller = "getservertemplatelist"; + metric.increment(ZMSConsts.HTTP_GET); + metric.increment(ZMSConsts.HTTP_REQUEST); + metric.increment(caller); + Object timerMetric = metric.startTiming("getservertemplatelist_timing", null); + + ServerTemplateList result = new ServerTemplateList(); + result.setTemplateNames(new ArrayList(serverSolutionTemplates.names())); + + metric.stopTiming(timerMetric); + return result; + } + + public Template getTemplate(ResourceContext ctx, String templateName) { + + final String caller = "gettemplate"; + metric.increment(ZMSConsts.HTTP_GET); + metric.increment(ZMSConsts.HTTP_REQUEST); + metric.increment(caller); + Object timerMetric = metric.startTiming("gettemplate_timing", null); + + validate(templateName, TYPE_SIMPLE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + templateName = templateName.toLowerCase(); + Template template = serverSolutionTemplates.get(templateName); + if (template == null) { + throw ZMSUtils.notFoundError("getTemplate: Template not found: '" + templateName + "'", caller); + } + + metric.stopTiming(timerMetric); + return template; + } + + // ----------------- the Role interface + + public RoleList getRoleList(ResourceContext ctx, String domainName, Integer limit, String skip) { + + final String caller = "getrolelist"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + if (skip != null) { + skip = skip.toLowerCase(); + } + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getrolelist_timing", domainName); + + RoleList result = new RoleList(); + + List names = new ArrayList(); + String next = processListRequest(domainName, AthenzObject.ROLE, limit, skip, names); + result.setNames(names); + if (next != null) { + result.setNext(next); + } + + metric.stopTiming(timerMetric); + return result; + } + + List setupRoleList(AthenzDomain domain, Boolean members) { + + // if we're asked to return the members as well then we + // just need to return the data as is without any modifications + + List roles = null; + if (members != null && members.booleanValue()) { + roles = domain.getRoles(); + } else { + roles = new ArrayList<>(); + for (Role role : domain.getRoles()) { + Role newRole = new Role() + .setName(role.getName()) + .setModified(role.getModified()) + .setTrust(role.getTrust()); + roles.add(newRole); + } + } + + return roles; + } + + public Roles getRoles(ResourceContext ctx, String domainName, Boolean members) { + + final String caller = "getroles"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getroles_timing", domainName); + + Roles result = new Roles(); + + AthenzDomain domain = getAthenzDomain(domainName, false); + if (domain == null) { + throw ZMSUtils.notFoundError("getRoles: Domain not found: '" + domainName + "'", caller); + } + + result.setList(setupRoleList(domain, members)); + metric.stopTiming(timerMetric); + return result; + } + + @Override + public Role getRole(ResourceContext ctx, String domainName, String roleName, + Boolean auditLog, Boolean expand) { + + final String caller = "getrole"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(roleName, TYPE_ENTITY_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + roleName = roleName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getrole_timing", domainName); + + Role role = dbService.getRole(domainName, roleName, auditLog, expand); + if (role == null) { + throw ZMSUtils.notFoundError("getRole: Role not found: '" + + ZMSUtils.roleResourceName(domainName, roleName) + "'", caller); + } + + metric.stopTiming(timerMetric); + return role; + } + + String getNormalizedMember(String member) { + + String[] yrnParts = member.split(":"); + if (yrnParts.length != 2) { + return member; + } + + // we are going we normalize and use the common name to + // represent our principals. The changes are: + // user:hga will be replaced with user.hga + // coretech:service.storage will be replaced with coretech.storage + + String normalizedMember = member; + if (yrnParts[0].equalsIgnoreCase(userDomain)) { + normalizedMember = userDomainPrefix + yrnParts[1]; + } else if (yrnParts[1].startsWith(SERVICE_PREFIX)) { + normalizedMember = yrnParts[0] + yrnParts[1].substring(SERVICE_PREFIX.length() - 1); + } + + return normalizedMember; + } + + void normalizeRoleMembers(Role role) { + + List members = role.getMembers(); + if (members == null || members.size() == 0) { + return; + } + + Set normalizedMembers = new HashSet<>(); + String normalizedMember = null; + for (String member : members) { + + normalizedMember = getNormalizedMember(member); + + // we'll automatically ignore any duplicates + + normalizedMembers.add(normalizedMember); + } + + role.setMembers(new ArrayList(normalizedMembers)); + return; + } + + boolean isConsistentRoleName(final String domainName, final String roleName, Role role) { + + String yrn = ZMSUtils.roleResourceName(domainName, roleName); + + // first lets assume we have the expected name specified in the role + + if (yrn.equals(role.getName())) { + return true; + } + + // if not check to see if the role contains the relative local name + // part only instead of the expected yrn and update accordingly + + if (roleName.equals(role.getName())) { + role.setName(yrn); + return true; + } + + // we have a mismatch + + return false; + } + + public Role putRole(ResourceContext ctx, String domainName, String roleName, String auditRef, Role role) { + + final String caller = "putrole"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(roleName, TYPE_ENTITY_NAME, caller); + validate(role, TYPE_ROLE, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + roleName = roleName.toLowerCase(); + AthenzObject.ROLE.convertToLowerCase(role); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("putrole_timing", domainName); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + // verify the role name in the URI and request are consistent + + if (!isConsistentRoleName(domainName, roleName, role)) { + throw ZMSUtils.requestError("putRole: Inconsistent role names - expected: " + + ZMSUtils.roleResourceName(domainName, roleName) + ", actual: " + + role.getName(), caller); + } + + // if this is a delegated role then validate that it's not + // delegated back to itself and there are no members since + // those 2 fields are mutually exclusive + + if (role.getTrust() != null && !role.getTrust().isEmpty()) { + + if (role.getMembers() != null && !role.getMembers().isEmpty()) { + throw ZMSUtils.requestError("putRole: Role cannot have both members and delegated domain set", caller); + } + + if (domainName.equals(role.getTrust())) { + throw ZMSUtils.requestError("putRole: Role cannot be delegated to itself", caller); + } + } + + // normalize and remove duplicate members + + normalizeRoleMembers(role); + + // process our request + + dbService.executePutRole(ctx, domainName, roleName, role, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, roleName, caller, ZMSConsts.HTTP_PUT, null, auditRef); + throw exc; + } + + return null; + } + + public Role deleteRole(ResourceContext ctx, String domainName, String roleName, String auditRef) { + + final String caller = "deleterole"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(roleName, TYPE_ENTITY_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + roleName = roleName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("deleterole_timing", domainName); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + /* we are not going to allow any user to delete + * the admin role and policy since those are required + * for standard domain operations */ + + if (roleName.equalsIgnoreCase(ADMIN_ROLE_NAME)) { + throw ZMSUtils.requestError("deleteRole: admin role cannot be deleted", caller); + } + + dbService.executeDeleteRole(ctx, domainName, roleName, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, roleName, caller, ZMSConsts.HTTP_DELETE, null, auditRef); + throw exc; + } + + return null; + } + + boolean isMemberOfRole(Role role, String member) { + + if (role.getMembers() == null) { + return false; + } + + Set members = new HashSet(role.getMembers()); + return members.contains(member); + } + + public Membership getMembership(ResourceContext ctx, String domainName, String roleName, String memberName) { + + final String caller = "getmembership"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(roleName, TYPE_ENTITY_NAME, caller); + validate(memberName, TYPE_RESOURCE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + roleName = roleName.toLowerCase(); + memberName = memberName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getmembership_timing", domainName); + + Membership result = dbService.getMembership(domainName, roleName, memberName); + + metric.stopTiming(timerMetric); + return result; + } + + public Membership putMembership(ResourceContext ctx, String domainName, String roleName, + String memberName, String auditRef, Membership membership) { + + final String caller = "putmembership"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(roleName, TYPE_ENTITY_NAME, caller); + validate(memberName, TYPE_RESOURCE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + roleName = roleName.toLowerCase(); + memberName = memberName.toLowerCase(); + AthenzObject.MEMBERSHIP.convertToLowerCase(membership); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("putmembership_timing", domainName); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), + caller, "role", roleName); + + // verify that the member name in the URI and object provided match + + if (!memberName.equals(membership.getMemberName())) { + throw ZMSUtils.requestError("putMembership: Member name in URI and Membership object do not match", caller); + } + + // role name is optional so we'll verify only if the value is present in the object + + if (membership.getRoleName() != null && !roleName.equals(membership.getRoleName())) { + throw ZMSUtils.requestError("putMembership: Role name in URI and Membership object do not match", caller); + } + + // add the member to the specified role + + dbService.executePutMembership(ctx, domainName, roleName, getNormalizedMember(memberName), auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified memberName=(").append(memberName).append(')'); + auditRequestFailure(ctx, exc, domainName, roleName, caller, ZMSConsts.HTTP_PUT, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + public Membership deleteMembership(ResourceContext ctx, String domainName, String roleName, + String memberName, String auditRef) { + + final String caller = "deletemembership"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(roleName, TYPE_ENTITY_NAME, caller); + validate(memberName, TYPE_RESOURCE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + roleName = roleName.toLowerCase(); + memberName = memberName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("deletemembership_timing", domainName); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + String normalizedMember = getNormalizedMember(memberName); + dbService.executeDeleteMembership(ctx, domainName, roleName, normalizedMember, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified memberName=(").append(memberName).append(')'); + auditRequestFailure(ctx, exc, domainName, roleName, caller, ZMSConsts.HTTP_DELETE, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + boolean hasExceededListLimit(Integer limit, int count) { + + if (limit == null) { + return false; + } + + if (limit > 0 && count > limit) { + return true; + } else { + return false; + } + } + + /** + * process the list request for the given object type - e.g. role, policy, etc + * if the limit is specified and we have reached that limit then return + * the name of the object that should be set at the next item for the + * subsequent list operation. + */ + String processListRequest(String domainName, AthenzObject objType, Integer limit, + String skip, List names) { + + switch (objType) { + case ROLE: + names.addAll(dbService.listRoles(domainName)); + break; + case POLICY: + names.addAll(dbService.listPolicies(domainName)); + break; + case SERVICE_IDENTITY: + names.addAll(dbService.listServiceIdentities(domainName)); + break; + default: + return null; + } + + int count = names.size(); + if (skip != null) { + for (int i = 0; i < count; i++) { + String name = names.get(i); + if (skip.equals(name)) { + names.subList(0, i + 1).clear(); + count = names.size(); + break; + } + } + } + + String next = null; + if (hasExceededListLimit(limit, count)) { + names.subList(limit, count).clear(); + next = names.get(limit - 1); + } + + return next; + } + + // ----------------- the Policy interface + + public PolicyList getPolicyList(ResourceContext ctx, String domainName, Integer limit, String skip) { + + final String caller = "getpolicylist"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + if (skip != null) { + skip = skip.toLowerCase(); + } + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getpolicylist_timing", domainName); + + List names = new ArrayList(); + String next = processListRequest(domainName, AthenzObject.POLICY, limit, skip, names); + PolicyList result = new PolicyList().setNames(names); + if (next != null) { + result.setNext(next); + } + + metric.stopTiming(timerMetric); + return result; + } + + List setupPolicyList(AthenzDomain domain, Boolean assertions) { + + // if we're asked to return the assertions as well then we + // just need to return the data as is without any modifications + + List policies = null; + if (assertions != null && assertions.booleanValue()) { + policies = domain.getPolicies(); + } else { + policies = new ArrayList<>(); + for (Policy policy : domain.getPolicies()) { + Policy newPolicy = new Policy() + .setName(policy.getName()) + .setModified(policy.getModified()); + policies.add(newPolicy); + } + } + + return policies; + } + + public Policies getPolicies(ResourceContext ctx, String domainName, Boolean assertions) { + + final String caller = "getpolicies"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getpolicies_timing", domainName); + + Policies result = new Policies(); + + AthenzDomain domain = getAthenzDomain(domainName, false); + if (domain == null) { + throw ZMSUtils.notFoundError("getPolicies: Domain not found: '" + domainName + "'", caller); + } + + result.setList(setupPolicyList(domain, assertions)); + metric.stopTiming(timerMetric); + return result; + } + + public Policy getPolicy(ResourceContext ctx, String domainName, String policyName) { + + final String caller = "getpolicy"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(policyName, TYPE_ENTITY_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + policyName = policyName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getpolicy_timing", domainName); + + Policy policy = dbService.getPolicy(domainName, policyName); + if (policy == null) { + throw ZMSUtils.notFoundError("getPolicy: Policy not found: '" + + ZMSUtils.policyResourceName(domainName, policyName) + "'", caller); + } + + metric.stopTiming(timerMetric); + return policy; + } + + public Assertion getAssertion(ResourceContext ctx, String domainName, String policyName, + Long assertionId) { + + final String caller = "getassertion"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(policyName, TYPE_ENTITY_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + policyName = policyName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getassertion_timing", domainName); + + Assertion assertion = dbService.getAssertion(domainName, policyName, assertionId); + if (assertion == null) { + throw ZMSUtils.notFoundError("getAssertion: Assertion not found: '" + + ZMSUtils.policyResourceName(domainName, policyName) + "' Assertion: '" + + assertionId + "'", caller); + } + + metric.stopTiming(timerMetric); + return assertion; + } + + public Assertion putAssertion(ResourceContext ctx, String domainName, String policyName, + String auditRef, Assertion assertion) { + + final String caller = "putassertion"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(policyName, TYPE_COMPOUND_NAME, caller); + validate(assertion, TYPE_ASSERTION, caller); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + policyName = policyName.toLowerCase(); + AthenzObject.ASSERTION.convertToLowerCase(assertion); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("putassertion_timing", domainName); + + // we are not going to allow any user to update + // the admin policy since that is required + // for standard domain operations */ + + if (policyName.equalsIgnoreCase(ADMIN_POLICY_NAME)) { + throw ZMSUtils.requestError("putAssertion: admin policy cannot be modified", caller); + } + + // validate to make sure we have expected values for assertion fields + + validatePolicyAssertion(assertion, caller); + + dbService.executePutAssertion(ctx, domainName, policyName, assertion, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, policyName, caller, ZMSConsts.HTTP_PUT, null, auditRef); + throw exc; + } + + return assertion; + } + + public Assertion deleteAssertion(ResourceContext ctx, String domainName, String policyName, + Long assertionId, String auditRef) { + + final String caller = "deleteassertion"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(policyName, TYPE_ENTITY_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + policyName = policyName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("deleteassertion_timing", domainName); + + // we are not going to allow any user to update + // the admin policy since that is required + // for standard domain operations */ + + if (policyName.equalsIgnoreCase(ADMIN_POLICY_NAME)) { + throw ZMSUtils.requestError("deleteAssertion: admin policy cannot be modified", caller); + } + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + dbService.executeDeleteAssertion(ctx, domainName, policyName, assertionId, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append("assertionId=(").append(assertionId).append(')'); + auditRequestFailure(ctx, exc, domainName, policyName, caller, ZMSConsts.HTTP_DELETE, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + void validatePolicyAssertions(List assertions, String caller) { + + if (assertions == null) { + return; + } + + for (Assertion assertion : assertions) { + validatePolicyAssertion(assertion, caller); + } + } + + void validatePolicyAssertion(Assertion assertion, String caller) { + + // extract the domain name from the resource + + String resource = assertion.getResource(); + int idx = resource.indexOf(':'); + if (idx == -1) { + throw ZMSUtils.requestError("Missing domain name from assertion resource: " + + resource, caller); + } + + // we need to validate our domain name with special + // case of * that is allowed to match any domain + + String domainName = resource.substring(0, idx); + if (!domainName.equals("*")) { + validate(domainName, TYPE_DOMAIN_NAME, caller); + } + } + + boolean isConsistentPolicyName(final String domainName, final String policyName, Policy policy) { + + String yrn = ZMSUtils.policyResourceName(domainName, policyName); + + // first lets assume we have the expected name specified in the policy + + if (yrn.equals(policy.getName())) { + return true; + } + + // if not check to see if the policy contains the relative local name + // part only instead of the expected yrn and update accordingly + + if (policyName.equals(policy.getName())) { + policy.setName(yrn); + return true; + } + + // we have a mismatch + + return false; + } + + public Policy putPolicy(ResourceContext ctx, String domainName, String policyName, String auditRef, Policy policy) { + + final String caller = "putpolicy"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(policyName, TYPE_COMPOUND_NAME, caller); + validate(policy, TYPE_POLICY, caller); + + // verify that request is properly authenticated for this request + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + policyName = policyName.toLowerCase(); + AthenzObject.POLICY.convertToLowerCase(policy); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("putpolicy_timing", domainName); + + // we are not going to allow any user to update + // the admin policy since that is required + // for standard domain operations */ + + if (policyName.equalsIgnoreCase(ADMIN_POLICY_NAME)) { + throw ZMSUtils.requestError("putPolicy: admin policy cannot be modified", caller); + } + + // verify the policy name in the URI and request are consistent + + if (!isConsistentPolicyName(domainName, policyName, policy)) { + throw ZMSUtils.requestError("putPolicy: Inconsistent policy names - expected: " + + ZMSUtils.policyResourceName(domainName, policyName) + ", actual: " + + policy.getName(), caller); + } + + // validate to make sure we have expected values for assertion fields + + validatePolicyAssertions(policy.getAssertions(), caller); + + dbService.executePutPolicy(ctx, domainName, policyName, policy, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, policyName, caller, ZMSConsts.HTTP_PUT, null, auditRef); + throw exc; + } + + return null; + } + + String auditListItems(String heading, List items) { + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(':').append(heading).append("=("); + boolean firstEntry = true; + if (items != null) { + for (String item : items) { + if (!firstEntry) { + sb.append(','); + } else { + firstEntry = false; + } + sb.append('\"').append(item).append('\"'); + } + } + sb.append(')'); + return sb.toString(); + } + + void auditRequestFailure(ResourceContext ctx, Exception exc, String domainName, String resourceName, + String caller, String method, String addlDetails, String auditRef) { + + AuditLogMsgBuilder msgBldr = getAuditLogMsgBuilder(ctx, domainName, auditRef, caller, method); + Timestamp when = Timestamp.fromCurrentTime(); + msgBldr.when(when.toString()).whatEntity(resourceName); + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append("ERROR=("); + if (exc instanceof ResourceException) { + ResourceError err = (ResourceError) ((ResourceException) exc).getData(); + sb.append(err.message); + } else { + sb.append(exc.getMessage()); + } + sb.append(')'); + if (addlDetails != null) { + sb.append(';').append(addlDetails); + } + msgBldr.whatDetails(sb.toString()); + auditLogger.log(msgBldr); + } + + public Policy deletePolicy(ResourceContext ctx, String domainName, String policyName, String auditRef) { + + final String caller = "deletepolicy"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(policyName, TYPE_ENTITY_NAME, caller); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + policyName = policyName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("deletepolicy_timing", domainName); + + // we are not going to allow any user to delete + // the admin role and policy since those are required + // for standard domain operations */ + + if (policyName.equalsIgnoreCase(ADMIN_POLICY_NAME)) { + throw ZMSUtils.requestError("deletePolicy: admin policy cannot be deleted", caller); + } + + dbService.executeDeletePolicy(ctx, domainName, policyName, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, policyName, caller, ZMSConsts.HTTP_DELETE, null, auditRef); + throw exc; + } + + return null; + } + + boolean matchDelegatedTrustAssertion(Assertion assertion, String roleName, + String roleMember, List roles) { + + if (!ZMSUtils.assumeRoleResourceMatch(roleName, assertion)) { + return false; + } + + String rolePattern = StringUtils.patternFromGlob(assertion.getRole()); + for (Role role : roles) { + String name = role.getName(); + if (!name.matches(rolePattern)) { + continue; + } + + if (isMemberOfRole(role, roleMember)) { + return true; + } + } + + return false; + } + + boolean matchDelegatedTrustPolicy(Policy policy, String roleName, String roleMember, List roles) { + + List assertions = policy.getAssertions(); + if (assertions == null) { + return false; + } + + for (Assertion assertion : assertions) { + if (matchDelegatedTrustAssertion(assertion, roleName, roleMember, roles)) { + return true; + } + } + + return false; + } + + boolean delegatedTrust(String domainName, String roleName, String roleMember) { + + AthenzDomain domain = getAthenzDomain(domainName, true); + if (domain == null) { + return false; + } + + for (Policy policy : domain.getPolicies()) { + if (matchDelegatedTrustPolicy(policy, roleName, roleMember, domain.getRoles())) { + return true; + } + } + + return false; + } + + boolean matchRole(String domain, List roles, String rolePattern, List authenticatedRoles) { + + if (LOG.isDebugEnabled()) { + LOG.debug("matchRole domain: " + domain + " rolePattern: " + rolePattern); + } + + String prefix = domain + ":role."; + int prefixLen = prefix.length(); + for (Role role : roles) { + String name = role.getName(); + if (!name.matches(rolePattern)) { + continue; + } + + String shortName = name.substring(prefixLen); + if (authenticatedRoles.contains(shortName)) { + return true; + } + } + return false; + } + + boolean shouldRunDelegatedTrustCheck(String trust, String trustDomain) { + + // if no trust field field then no delegated trust check + + if (trust == null) { + return false; + } + + // if no specific trust domain specifies then we need + // run the delegated trust check for this domain + + if (trustDomain == null) { + return true; + } + + // otherwise we'll run the delegated trust check only if + // domain name matches + + return trust.equalsIgnoreCase(trustDomain); + } + + boolean matchPrincipalInRole(Role role, String roleName, String fullUser, String trustDomain) { + + // if we have members in the role then we're going to check + // against that list only + + if (role.getMembers() != null) { + return isMemberOfRole(role, fullUser); + } + + // no members so let's check if this is a trust domain + + String trust = role.getTrust(); + if (!shouldRunDelegatedTrustCheck(trust, trustDomain)) { + return false; + } + + // delegate to another domain. + + if (LOG.isDebugEnabled()) { + LOG.debug("matchPrincipal: [delegated trust. Checking with: " + trust + "]"); + } + + return delegatedTrust(trust, roleName, fullUser); + } + + boolean matchPrincipal(List roles, String rolePattern, String fullUser, String trustDomain) { + + if (LOG.isDebugEnabled()) { + LOG.debug("matchPrincipal - rolePattern: " + rolePattern + " user: " + fullUser + + " trust: " + trustDomain); + } + + for (Role role : roles) { + + String name = role.getName(); + if (!name.matches(rolePattern)) { + continue; + } + + if (matchPrincipalInRole(role, name, fullUser, trustDomain)) { + if (LOG.isDebugEnabled()) { + LOG.debug("assertionMatch: -> OK (by principal)"); + } + return true; + } + } + return false; + } + + AthenzDomain virtualHomeDomain(Principal principal) { + + if (LOG.isDebugEnabled()) { + LOG.debug("homeDomain: home domain detected. Create on the fly."); + } + + String name = principal.getYRN(); + AthenzDomain athenzDomain = new AthenzDomain(name); + + List adminUsers = new ArrayList<>(); + adminUsers.add(name); + + Role role = ZMSUtils.makeAdminRole(name, adminUsers); + athenzDomain.getRoles().add(role); + + Policy policy = ZMSUtils.makeAdminPolicy(name, role); + athenzDomain.getPolicies().add(policy); + + return athenzDomain; + } + + boolean assertionMatch(Assertion assertion, String identityYRN, String action, String resource, + List roles, List authenticatedRoles, String trustDomain) { + + String actionPattern = StringUtils.patternFromGlob(assertion.getAction()); + if (!action.matches(actionPattern)) { + return false; + } + + String rezPattern = StringUtils.patternFromGlob(assertion.getResource()); + if (!resource.matches(rezPattern)) { + return false; + } + + boolean matchResult = false; + String rolePattern = StringUtils.patternFromGlob(assertion.getRole()); + if (authenticatedRoles != null) { + matchResult = matchRole(trustDomain, roles, rolePattern, authenticatedRoles); + } else { + matchResult = matchPrincipal(roles, rolePattern, identityYRN, trustDomain); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("assertionMatch: -> " + matchResult + + " (effect: " + assertion.getEffect() + ")"); + } + + return matchResult; + } + + boolean verifyProviderEndpoint(String providerEndpoint) { + + // verify that we have a valid endpoint that ends in + // yahoo domain. if it's not present or an empty + // value then there is no field to verify + + if (providerEndpoint == null) { + return true; + } + + if (providerEndpoint.isEmpty()) { + return true; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("verifyProviderEndpoint: verifying endpoint: " + providerEndpoint); + } + + java.net.URI uri = null; + try { + uri = new java.net.URI(providerEndpoint); + } catch (URISyntaxException ex) { + return false; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("verifyProviderEndpoint: host: " + uri.getHost() + " scheme: " + uri.getScheme()); + } + + // we're going to allow localhost as a special case since + // that's often used for dev testing + + String host = uri.getHost(); + if (host == null) { + return false; + } + host = host.toLowerCase(); + + boolean valid = host.equals(ZMSConsts.LOCALHOST); + if (!valid && providerEndpoints != null) { + for (String endpoint : providerEndpoints) { + valid = host.endsWith(endpoint); + if (valid) { + break; + } + } + } + + if (valid) { + String scheme = uri.getScheme(); + if (scheme == null) { + return false; + } + valid = scheme.equalsIgnoreCase(ZMSConsts.HTTP_SCHEME) || + scheme.equalsIgnoreCase(ZMSConsts.HTTPS_SCHEME); + } + + return valid; + } + + boolean verifyServicePublicKey(String key) { + try { + PublicKey pub = Crypto.loadPublicKey(Crypto.ybase64DecodeString(key)); + if (LOG.isDebugEnabled()) { + LOG.debug("verifyServicePublicKey: public key looks valid: " + pub); + } + } catch (Exception ex) { + LOG.error("verifyServicePublicKey: Invalid Public Key: " + ex.getMessage()); + return false; + } + return true; + } + + boolean verifyServicePublicKeys(ServiceIdentity service) { + + // verify that the public keys specified are valid public + // key and we require that at least one key is provided + + List publicKeyList = service.getPublicKeys(); + if (publicKeyList == null || publicKeyList.size() == 0) { + return false; + } + for (PublicKeyEntry entry : publicKeyList) { + if (!verifyServicePublicKey(entry.getKey())) { + return false; + } + } + return true; + } + + // ----------------- the ServiceIdentity interface + + public ServiceIdentity putServiceIdentity(ResourceContext ctx, String domainName, String serviceName, + String auditRef, ServiceIdentity service) { + + final String caller = "putserviceidentity"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(serviceName, TYPE_SIMPLE_NAME, caller); + validate(service, TYPE_SERVICE_IDENTITY, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + serviceName = serviceName.toLowerCase(); + AthenzObject.SERVICE_IDENTITY.convertToLowerCase(service); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("putserviceidentity_timing", domainName); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + if (!ZMSUtils.serviceResourceName(domainName, serviceName).equals(service.getName())) { + throw ZMSUtils.requestError("putServiceIdentity: Inconsistent service/domain names", caller); + } + + if (!verifyServicePublicKeys(service)) { + throw ZMSUtils.requestError("putServiceIdentity: No valid public key found or provided public key is invalid", caller); + } + + if (!verifyProviderEndpoint(service.getProviderEndpoint())) { + throw ZMSUtils.requestError("putServiceIdentity: Invalid endpoint: " + + service.getProviderEndpoint() + " - must be http/https and in yahoo domain", caller); + } + + dbService.executePutServiceIdentity(ctx, domainName, serviceName, service, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, serviceName, caller, ZMSConsts.HTTP_PUT, null, auditRef); + throw exc; + } + + return null; + } + + public ServiceIdentity getServiceIdentity(ResourceContext ctx, String domainName, String serviceName) { + + final String caller = "getserviceidentity"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(serviceName, TYPE_SIMPLE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + serviceName = serviceName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getserviceidentity_timing", domainName); + + ServiceIdentity service = dbService.getServiceIdentity(domainName, serviceName); + if (service == null) { + throw ZMSUtils.notFoundError("getServiceIdentity: Service not found: '" + + ZMSUtils.serviceResourceName(domainName, serviceName) + "'", caller); + } + + metric.stopTiming(timerMetric); + return service; + } + + public ServiceIdentity deleteServiceIdentity(ResourceContext ctx, String domainName, + String serviceName, String auditRef) { + + final String caller = "deleteserviceidentity"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(serviceName, TYPE_SIMPLE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + serviceName = serviceName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("deleteserviceidentity_timing", domainName); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + dbService.executeDeleteServiceIdentity(ctx, domainName, serviceName, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, domainName, serviceName, caller, ZMSConsts.HTTP_DELETE, null, auditRef); + throw exc; + } + + return null; + } + + List setupServiceIdentityList(AthenzDomain domain, Boolean publicKeys, Boolean hosts) { + + // if we're asked to return the public keys and hosts as well then we + // just need to return the data as is without any modifications + + List services = null; + if (publicKeys != null && publicKeys.booleanValue() && hosts != null && hosts.booleanValue()) { + services = domain.getServices(); + } else { + services = new ArrayList<>(); + for (ServiceIdentity service : domain.getServices()) { + ServiceIdentity newService = new ServiceIdentity() + .setName(service.getName()) + .setModified(service.getModified()) + .setExecutable(service.getExecutable()) + .setGroup(service.getGroup()) + .setUser(service.getUser()) + .setProviderEndpoint(service.getProviderEndpoint()); + if (publicKeys != null && publicKeys.booleanValue()) { + newService.setPublicKeys(service.getPublicKeys()); + } else if (hosts != null && hosts.booleanValue()) { + newService.setHosts(service.getHosts()); + } + services.add(newService); + } + } + + return services; + } + + public ServiceIdentities getServiceIdentities(ResourceContext ctx, String domainName, + Boolean publicKeys, Boolean hosts) { + + final String caller = "getserviceidentities"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getserviceidentities_timing", domainName); + + ServiceIdentities result = new ServiceIdentities(); + + AthenzDomain domain = getAthenzDomain(domainName, false); + if (domain == null) { + throw ZMSUtils.notFoundError("getServiceIdentities: Domain not found: '" + + domainName + "'", caller); + } + + result.setList(setupServiceIdentityList(domain, publicKeys, hosts)); + metric.stopTiming(timerMetric); + return result; + } + + public ServiceIdentityList getServiceIdentityList(ResourceContext ctx, String domainName, + Integer limit, String skip) { + + final String caller = "getserviceidentitylist"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + if (skip != null) { + skip = skip.toLowerCase(); + } + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getserviceidentitylist_timing", domainName); + + List names = new ArrayList(); + String next = processListRequest(domainName, AthenzObject.SERVICE_IDENTITY, limit, skip, names); + ServiceIdentityList result = new ServiceIdentityList().setNames(names); + if (next != null) { + result.setNext(next); + } + + metric.stopTiming(timerMetric); + return result; + } + + public PublicKeyEntry getPublicKeyEntry(ResourceContext ctx, String domainName, String serviceName, String keyId) { + + final String caller = "getpublickeyentry"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(serviceName, TYPE_SIMPLE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + serviceName = serviceName.toLowerCase(); + keyId = keyId.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getpublickeyentry_timing", domainName); + + PublicKeyEntry entry = dbService.getServicePublicKeyEntry(domainName, serviceName, keyId); + if (entry == null) { + throw ZMSUtils.notFoundError("getPublicKeyEntry: PublicKey " + keyId + " in service " + + ZMSUtils.serviceResourceName(domainName, serviceName) + " not found", caller); + } + + metric.stopTiming(timerMetric); + return entry; + } + + public PublicKeyEntry deletePublicKeyEntry(ResourceContext ctx, String domainName, String serviceName, + String keyId, String auditRef) { + + final String caller = "deletepublickeyentry"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(serviceName, TYPE_SIMPLE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + serviceName = serviceName.toLowerCase(); + keyId = keyId.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("deletepublickeyentry_timing", domainName); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + dbService.executeDeletePublicKeyEntry(ctx, domainName, serviceName, keyId, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified keyId=(").append(keyId).append(')'); + auditRequestFailure(ctx, exc, domainName, serviceName, caller, ZMSConsts.HTTP_DELETE, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + public PublicKeyEntry putPublicKeyEntry(ResourceContext ctx, String domainName, String serviceName, + String keyId, String auditRef, PublicKeyEntry keyEntry) { + + final String caller = "putpublickeyentry"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(serviceName, TYPE_SIMPLE_NAME, caller); + validate(keyEntry, TYPE_PUBLIC_KEY_ENTRY, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + serviceName = serviceName.toLowerCase(); + keyId = keyId.toLowerCase(); + AthenzObject.PUBLIC_KEY_ENTRY.convertToLowerCase(keyEntry); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("putpublickeyentry_timing", domainName); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + // verify that key id specified in request and object do match + + if (!keyId.equals(keyEntry.getId())) { + throw ZMSUtils.requestError("putPublicKeyEntry: keyId in URI and PublicKeyEntry object do not match", caller); + } + + // verify we have a valid public key specified + + if (!verifyServicePublicKey(keyEntry.getKey())) { + throw ZMSUtils.requestError("putPublicKeyEntry: Invalid public key", caller); + } + + dbService.executePutPublicKeyEntry(ctx, domainName, serviceName, keyEntry, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified keyId=(").append(keyId).append(')'); + auditRequestFailure(ctx, exc, domainName, serviceName, caller, ZMSConsts.HTTP_PUT, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + String removeQuotes(String value) { + if (value.startsWith("\"")) { + value = value.substring(1, value.length()); + } + if (value.endsWith("\"")) { + value = value.substring(0, value.length() - 1); + } + return value; + } + + long getModTimestamp(String matchingTag) { + + long timestamp = 0; + if (matchingTag == null) { + return timestamp; + } + + matchingTag = removeQuotes(matchingTag); + + if (LOG.isDebugEnabled()) { + LOG.debug("getModTimestamp: matching tag (" + matchingTag + ")"); + } + + try { + Timestamp tagStamp = Timestamp.fromString(matchingTag); + if (tagStamp == null) { + throw new IllegalArgumentException("Timestamp failed"); + } + timestamp = tagStamp.millis(); + } catch (IllegalArgumentException exc) { + if (LOG.isWarnEnabled()) { + LOG.warn("getModTimestamp: matching tag(" + matchingTag + ") has bad format. Return -1L by default."); + } + } + + return timestamp; + } + + // SignedDomains interface + public void getSignedDomains(ResourceContext context, String domain, String metaOnly, + String matchingTag, GetSignedDomainsResult result) { + + final String caller = "getsigneddomains"; + metric.increment(ZMSConsts.HTTP_GET); + metric.increment(ZMSConsts.HTTP_REQUEST); + metric.increment(caller); + Object timerMetric = metric.startTiming("getsigneddomains_timing", null); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + if (domain != null) { + domain = domain.toLowerCase(); + } + + boolean setMetaDataOnly = false; + if (metaOnly != null) { + // only true or false is valid + setMetaDataOnly = metaOnly.trim().equalsIgnoreCase("true"); + if (LOG.isDebugEnabled()) { + LOG.debug("getSignedDomains: metaonly: " + metaOnly, caller); + } + } + + long timestamp = getModTimestamp(matchingTag); + + // we should get our matching tag before calling get modified list + // in case we get a domain added/updated right after an empty domain list + // was returned and before the matchingTag was set to a value + + if (matchingTag == null) { + EntityTag eTag = new EntityTag(Timestamp.fromMillis(0).toString()); + matchingTag = eTag.toString(); + } + + DomainModifiedList dmlist = dbService.listModifiedDomains(timestamp); + List modlist = dmlist.getNameModList(); + if (modlist == null || modlist.size() == 0) { + result.done(304, matchingTag); + } + + Long youngestDomMod = -1L; + List sdList = new ArrayList(); + + // now we can iterate through our list and retrieve each domain + + boolean domainFilterDone = false; + for (DomainModified dmod : modlist) { + + // if we were processing only our given domain then + // we can stop iterating through the list if the + // filter done flag has been set + + if (domainFilterDone) { + break; + } + + // if we're given a specific domain then ignore all others + + if (domain != null && !domain.isEmpty()) { + if (domain.compareToIgnoreCase(dmod.getName()) != 0) { + continue; + } + domainFilterDone = true; + } + + Long domModMillis = dmod.getModified(); + if (domModMillis.compareTo(youngestDomMod) > 0) { + youngestDomMod = domModMillis; + } + + // generate our signed domain object + + SignedDomain signedDomain = new SignedDomain(); + DomainData domainData = new DomainData().setName(dmod.getName()); + signedDomain.setDomain(domainData); + domainData.setModified(Timestamp.fromMillis(dmod.getModified())); + + // check if we're asked to only return the meta data which + // we already have - name and last modified time, so we can + // add the domain to our return list and continue with the + // next domain + + if (setMetaDataOnly) { + sdList.add(signedDomain); + continue; + } + + // get the policies, roles, and service identities to create the + // DomainData + + if (LOG.isDebugEnabled()) { + LOG.debug("getSignedDomains: retrieving domain " + dmod.getName()); + } + + AthenzDomain athenzDomain = getAthenzDomain(dmod.getName(), true); + + // it's possible that our domain was deleted by another + // thread while we were processing this request so + // if we get a null object, we'll just skip this + // item and continue with the next one + + if (athenzDomain == null) { + continue; + } + + // we have a valid domain so first we need to add + // our object to the return list + + sdList.add(signedDomain); + + // set domain attributes + + domainData.setAccount(athenzDomain.getDomain().getAccount()); + domainData.setYpmId(athenzDomain.getDomain().getYpmId()); + domainData.setRoles(athenzDomain.getRoles()); + domainData.setServices(athenzDomain.getServices()); + + // generate the domain policy object that includes the domain + // name and all policies. Then we'll sign this struct using + // server's private key to get signed policy object + + DomainPolicies domainPolicies = new DomainPolicies().setDomain(dmod.getName()); + domainPolicies.setPolicies(getPolicyListWithoutAssertionId(athenzDomain.getPolicies())); + SignedPolicies signedPolicies = new SignedPolicies(); + signedPolicies.setContents(domainPolicies); + domainData.setPolicies(signedPolicies); + + String signature = Crypto.sign(SignUtils.asCanonicalString(signedDomain.getDomain().getPolicies().getContents()), privateKey); + signedDomain.getDomain().getPolicies().setSignature(signature).setKeyId(privateKeyId); + + // then sign the data and set the data and signature in a SignedDomain + + signature = Crypto.sign(SignUtils.asCanonicalString(signedDomain.getDomain()), privateKey); + signedDomain.setSignature(signature).setKeyId(privateKeyId); + } + + SignedDomains sdoms = new SignedDomains(); + sdoms.setDomains(sdList); + + Timestamp youngest = Timestamp.fromMillis(youngestDomMod); + EntityTag eTag = new EntityTag(youngest.toString()); + + metric.stopTiming(timerMetric); + result.done(200, sdoms, eTag.toString()); + } + + List getPolicyListWithoutAssertionId(List policies) { + + if (policies == null) { + return null; + } + + // we are going to remove the assertion id from our assertions + // since the data is signed and the clients don't need to be + // updated due to this new attribute being returned + + List policyList = new ArrayList<>(); + + for (Policy policy : policies) { + Policy newPolicy = new Policy() + .setModified(policy.getModified()) + .setName(policy.getName()); + if (policy.getAssertions() != null) { + List assertions = new ArrayList<>(); + for (Assertion assertion : policy.getAssertions()) { + Assertion newAssertion = new Assertion() + .setAction(assertion.getAction()) + .setResource(assertion.getResource()) + .setRole(assertion.getRole()); + if (assertion.getEffect() != null) { + newAssertion.setEffect(assertion.getEffect()); + } else { + newAssertion.setEffect(AssertionEffect.ALLOW); + } + assertions.add(newAssertion); + } + newPolicy.setAssertions(assertions); + } + policyList.add(newPolicy); + } + return policyList; + } + + boolean isValidUserTokenRequest(Principal principal, String userName) { + + if (principal == null) { + return false; + } + + Authority authority = principal.getAuthority(); + if (authority == null) { + return false; + } + + // if authority allowed to carry out authorization checks there + // is no need to request user tokens + + if (authority.allowAuthorization()) { + if (LOG.isDebugEnabled()) { + LOG.debug("User Token request - Authority cannot request user tokens"); + } + return false; + } + + String authDomain = authority.getDomain(); + if (authDomain == null || !authDomain.equalsIgnoreCase(userDomain)) { + if (LOG.isDebugEnabled()) { + LOG.debug("User Token request - not authenticated by User Authority"); + } + return false; + } + + // if the username is not our pre-defined skip value we are going + // to verify that it matches to the principal's name + + if (userName.equalsIgnoreCase(USER_TOKEN_DEFAULT_NAME)) { + return true; + } + + if (!userName.equalsIgnoreCase(principal.getName())) { + if (LOG.isDebugEnabled()) { + LOG.debug("User Token request - mismatch between request user name and userid"); + } + return false; + } + + return true; + } + + public UserToken getUserToken(ResourceContext ctx, String userName, String authorizedServices) { + + final String caller = "getusertoken"; + metric.increment(ZMSConsts.HTTP_GET); + metric.increment(ZMSConsts.HTTP_REQUEST); + metric.increment(caller); + Object timerMetric = metric.startTiming("getusertoken_timing", null); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + userName = userName.toLowerCase(); + + Principal principal = ((RsrcCtxWrapper) ctx).principal(); + if (!isValidUserTokenRequest(principal, userName)) { + throw ZMSUtils.unauthorizedError("getUserToken: Invalid request - missing User credentials or userName mismatch", caller); + } + + // if the user is requesting authorized services we need to verify that + // all the service names are valid + + List services = null; + if (authorizedServices != null && !authorizedServices.isEmpty()) { + services = Arrays.asList(authorizedServices.split(",")); + for (String service : services) { + if (!serverAuthorizedServices.contains(service)) { + throw ZMSUtils.unauthorizedError("getUserToken: Service " + service + " is not authorized in ZMS", caller); + } + } + } + + PrincipalToken token = new PrincipalToken.Builder("U1", userDomain, principal.getName()) + .expirationWindow(userTokenTimeout).keyId(privateKeyId).host(serverHostName) + .ip(ServletRequestUtil.getRemoteAddress(ctx.request())).authorizedServices(services).build(); + + token.sign(privateKey); + UserToken userToken = new UserToken().setToken(token.getSignedToken()); + + // set our standard CORS headers in our response if we're processing + // a get user token for an authorized service + + if (services != null) { + setStandardCORSHeaders(ctx); + } + + metric.stopTiming(timerMetric); + return userToken; + } + + public UserToken optionsUserToken(ResourceContext ctx, String userName, String authorizedServices) { + + final String caller = "optionsusertoken"; + metric.increment(ZMSConsts.HTTP_OPTIONS); + metric.increment(ZMSConsts.HTTP_REQUEST); + metric.increment(caller); + Object timerMetric = metric.startTiming("optionsusertoken_timing", null); + + // if the user must be requesting authorized service token + + if (authorizedServices == null || authorizedServices.isEmpty()) { + throw ZMSUtils.requestError("optionsUserToken: No authorized services specified in the request", caller); + } + + // verify that all specified services are valid + + List services = Arrays.asList(authorizedServices.split(",")); + for (String service : services) { + if (!serverAuthorizedServices.contains(service)) { + throw ZMSUtils.requestError("optionsUserToken: Service " + service + " is not authorized in ZMS", caller); + } + } + + // set our standard CORS headers in our response + + setStandardCORSHeaders(ctx); + + // since this is the preflight request we are going to report that + // we only allow GET method and configure the user-agent to cache + // this request results for up-to 30 days + + ctx.response().addHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_METHODS, ZMSConsts.HTTP_GET); + ctx.response().addHeader(ZMSConsts.HTTP_ACCESS_CONTROL_MAX_AGE, "2592000"); + + metric.stopTiming(timerMetric); + return null; + } + + void setStandardCORSHeaders(ResourceContext ctx) { + + // if we get an Origin header in our request then we're going to return + // the same value in the Allow-Origin header otherwise we'll just + // return * value to match everything + + String origin = ctx.request().getHeader(ZMSConsts.HTTP_ORIGIN); + if (origin != null && !origin.isEmpty()) { + ctx.response().addHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_ORIGIN, origin); + } else { + ctx.response().addHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_ORIGIN, "*"); + } + + // we must allow credentials to be passed by the client + + ctx.response().addHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); + + // if the client is asking us to allow any headers then we're going + // to return that set back as allowed + + String allowHeaders = ctx.request().getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_REQUEST_HEADERS); + if (allowHeaders != null && !allowHeaders.isEmpty()) { + ctx.response().addHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders); + } + } + + // Tenancy interface + + String providerServiceDomain(String provider) { + int n = provider.lastIndexOf('.'); + if (n <= 0 || n == provider.length() - 1) { + return null; + } + return provider.substring(0, n); + } + + String providerServiceName(String provider) { + int n = provider.lastIndexOf('.'); + if (n <= 0 || n == provider.length() - 1) { + return null; + } + return provider.substring(n + 1); + } + + ProviderClient getProviderClient(String url, Principal tenantAdmin) { + + final String caller = "getproviderclient"; + + ProviderClient prov = null; + if (providerClass == null) { + prov = new ProviderClient(url); + prov.addCredentials(tenantAdmin.getAuthority().getHeader(), tenantAdmin.getCredentials()); + } else { + try { + prov = providerClass.getConstructor(new Class[] { String.class, Principal.class }) + .newInstance(url, tenantAdmin); + } catch (Exception e) { + throw ZMSUtils.requestError("getProviderClient: Provider Class does not have the appropriate constructor", caller); + } + } + + return prov; + } + + public Tenancy putTenancy(ResourceContext ctx, String tenantDomain, String provider, + String auditRef, Tenancy detail) { + + final String caller = "puttenancy"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(provider, TYPE_SERVICE_NAME, caller); //the fully qualified service name to provision on + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + tenantDomain = tenantDomain.toLowerCase(); + provider = provider.toLowerCase(); + AthenzObject.TENANCY.convertToLowerCase(detail); + + metric.increment(ZMSConsts.HTTP_REQUEST, tenantDomain); + metric.increment(caller, tenantDomain); + Object timerMetric = metric.startTiming("puttenancy_timing", tenantDomain); + + // verify that request is properly authenticated for this request + + String authorizedService = ((RsrcCtxWrapper) ctx).principal().getAuthorizedService(); + verifyAuthorizedServiceOperation(authorizedService, caller); + + final String logPrefix = "putTenancy: tenant domain(" + tenantDomain + "): "; + + if (LOG.isInfoEnabled()) { + LOG.info("---- BEGIN put Tenant on provider(" + provider + ", ...)"); + } + + String provSvcDomain = providerServiceDomain(provider); // provider service domain + String provSvcName = providerServiceName(provider); // provider service name + + ServiceIdentity ent = dbService.getServiceIdentity(provSvcDomain, provSvcName); + if (ent == null) { + throw ZMSUtils.requestError(logPrefix + "Unable to retrieve service=" + provider, caller); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("serviceIdentity: provider=" + ent); + } + + // we are going to allow the authorize service token owner to call + // put tenancy on its own service without configuring a controller + // end point + + boolean authzServiceTokenOperation = isAuthorizedProviderService(authorizedService, + provSvcDomain, provSvcName, tenantDomain, auditRef); + + String url = ent.getProviderEndpoint(); + if ((url == null || url.isEmpty()) && !authzServiceTokenOperation) { + throw ZMSUtils.requestError(logPrefix + "Cannot put tenancy on provider service=" + + provider + " -- not a provider service", caller); + } + + if (LOG.isInfoEnabled()) { + LOG.info("let's talk to the provider on this endpoint: " + url); + } + + //ok, set up the policy for trust so the provider can check it + + if (LOG.isInfoEnabled()) { + LOG.info("---- set up the ASSUME_ROLE for admin, so provider can check I'm an admin"); + } + + // set up our tenant admin policy so provider can check admin's access + + dbService.setupTenantAdminPolicy(ctx, tenantDomain, provSvcDomain, + provSvcName, auditRef, caller); + + // if this is an authorized service token request then we're going to create + // the corresponding admin role in the provider domain since that's been + // authenticated already. otherwise, we're going to continue and process + // a standard provider controller based implementation when we contact the + // controller to complete the tenancy request + + if (authzServiceTokenOperation) { + + List roles = new ArrayList<>(); + TenantRoleAction roleAction = new TenantRoleAction().setAction("*").setRole(ADMIN_ROLE_NAME); + roles.add(roleAction); + dbService.executePutTenantRoles(ctx, provSvcDomain, provSvcName, tenantDomain, null, + roles, auditRef, caller); + + } else { + + Principal tenantAdmin = ((RsrcCtxWrapper) ctx).principal(); + if (LOG.isInfoEnabled()) { + LOG.info("---- now tell the provider to setTenant, as " + tenantAdmin.getYRN() + + ", creds = " + tenantAdmin.getCredentials()); + } + + Tenant tenant = new Tenant().setService(provSvcName).setName(tenantDomain); + Tenant tenantWithRoles = null; + try { + ProviderClient prov = getProviderClient(url, tenantAdmin); + tenantWithRoles = prov.putTenant(provSvcName, tenantDomain, auditRef, tenant); + } catch (Exception exc) { + throw ZMSUtils.requestError(logPrefix + "Failed to put tenant on provider service(" + + provider + "): " + exc.getMessage(), caller); + } + + if (LOG.isInfoEnabled()) { + LOG.info("---- result of provider.putTenant: " + tenantWithRoles); + } + + // now set up the roles and policies for all the provider roles returned + // if the provider supports resource groups, during the putTenant call + // we're just setting up tenancy and as such we won't get back any roles + + List providerRoles = tenantWithRoles.getRoles(); + if (providerRoles != null && !providerRoles.isEmpty()) { + + // we're going to create a separate role for each one of tenant roles returned + // based on its action and set the caller as a member in each role + + dbService.executePutProviderRoles(ctx, tenantDomain, provSvcDomain, provSvcName, null, + providerRoles, auditRef, caller); + } + } + + if (LOG.isInfoEnabled()) { + LOG.info("---- END put Tenant -> " + detail); + } + + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified tenant-domain-(").append(tenantDomain).append(')'); + auditRequestFailure(ctx, exc, tenantDomain, provider, caller, ZMSConsts.HTTP_PUT, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + public TenancyResourceGroup putTenancyResourceGroup(ResourceContext ctx, String tenantDomain, String provider, + String resourceGroup, String auditRef, TenancyResourceGroup detail) { + + final String caller = "puttenancyresourcegroup"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(provider, TYPE_SERVICE_NAME, caller); //the fully qualified service name to provision on + validate(resourceGroup, TYPE_COMPOUND_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + tenantDomain = tenantDomain.toLowerCase(); + provider = provider.toLowerCase(); + resourceGroup = resourceGroup.toLowerCase(); + AthenzObject.TENANCY_RESOURCE_GROUP.convertToLowerCase(detail); + + metric.increment(ZMSConsts.HTTP_REQUEST, tenantDomain); + metric.increment(caller, tenantDomain); + Object timerMetric = metric.startTiming("puttenancyresourcegroup_timing", tenantDomain); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + if (LOG.isDebugEnabled()) { + LOG.debug("putTenancyResourceGroup: tenant domain(" + tenantDomain + + ") resourceGroup(" + resourceGroup + ")"); + } + + String provSvcDomain = providerServiceDomain(provider); // provider service domain + String provSvcName = providerServiceName(provider); // provider service name + + ServiceIdentity ent = dbService.getServiceIdentity(provSvcDomain, provSvcName); + if (ent == null) { + throw ZMSUtils.requestError("Unable to retrieve service=" + provider, caller); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("serviceIdentity: provider=" + ent); + } + String url = ent.getProviderEndpoint(); + if (url == null || url.isEmpty()) { + throw ZMSUtils.requestError("Cannot put tenancy resource group on provider service=" + + provider + " -- not a provider service", caller); + } + + Principal tenantAdmin = ((RsrcCtxWrapper) ctx).principal(); + + TenantResourceGroup tenantResourceGroup = new TenantResourceGroup(); + tenantResourceGroup.setService(provSvcName).setName(tenantDomain).setResourceGroup(resourceGroup); + + TenantResourceGroup tenantWithRoles = null; + try { + ProviderClient prov = getProviderClient(url, tenantAdmin); + tenantWithRoles = prov.putTenantResourceGroup(provSvcName, tenantDomain, resourceGroup, + auditRef, tenantResourceGroup); + } catch (Exception exc) { + throw ZMSUtils.requestError("Failed to put tenant resource group(" + resourceGroup + + ") on provider service(" + provider + "): " + exc.getMessage(), caller); + } + + if (LOG.isInfoEnabled()) { + LOG.info("---- result of provider.putTenantResourceGroup: " + tenantWithRoles); + } + + List providerRoles = tenantWithRoles.getRoles(); + if (providerRoles == null || providerRoles.isEmpty()) { + throw ZMSUtils.requestError("Provider Controller did not return any roles to provision", caller); + } + + // we're going to create a separate role for each one of tenant roles returned + // based on its action and set the caller as a member in each role + + dbService.executePutProviderRoles(ctx, tenantDomain, provSvcDomain, provSvcName, resourceGroup, + providerRoles, auditRef, caller); + + if (LOG.isInfoEnabled()) { + LOG.info("---- END put Tenant Resource Group -> " + detail); + } + + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified resource-group=(").append(resourceGroup).append(')'); + auditRequestFailure(ctx, exc, tenantDomain, provider, caller, ZMSConsts.HTTP_PUT, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + boolean verifyTenancyPolicies(String tenantDomain, List tenantPolicies, Set providerPolicies, + String provSvcDomain, String provSvcName, String resourceGroup) { + + // generate the tenant policy name + + StringBuilder nameBuilder = new StringBuilder(256); + nameBuilder.append("tenancy.") + .append(ZMSUtils.getProviderResourceGroupRolePrefix(provSvcDomain, provSvcName, resourceGroup)); + String pnamePrefix = nameBuilder.toString(); + String rsrcMatchStr = ":role."; + int rsrcMatchStrLen = rsrcMatchStr.length(); + + String provPolName = null; + for (String pname : tenantPolicies) { + if (pname.startsWith(pnamePrefix)) { + Policy pol = dbService.getPolicy(tenantDomain, pname); + if (pol == null) { + break; + } + List assertions = pol.getAssertions(); + if (assertions != null) { + for (Assertion assertion : assertions) { + if (ZMSConsts.ACTION_ASSUME_ROLE.equalsIgnoreCase(assertion.getAction())) { + String rsrc = assertion.getResource(); + // parse rsrc, ex: "weather:role.storage.tenant.sports.deleter" + int index = rsrc.indexOf(rsrcMatchStr); + if (index > -1) { + rsrc = rsrc.substring(index + rsrcMatchStrLen); + } + provPolName = rsrc; + break; + } + } + } + } + } + + if (provPolName == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("verifyTenancyPolicies: No ASSUME_ROLE with policy prefix: " + pnamePrefix); + } + return false; + } + + // verify the tenant is in the provider too + // look for the policy in the provider + + if (!providerPolicies.contains(provPolName)) { + if (LOG.isDebugEnabled()) { + LOG.debug("verifyTenancyPolicies: No tenant policy in provider: " + provPolName); + } + return false; + } + + return true; + } + + public Tenancy getTenancy(ResourceContext ctx, String tenantDomain, String providerService) { + + final String caller = "gettenancy"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(providerService, TYPE_SERVICE_NAME, caller); // fully qualified provider's service name + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + tenantDomain = tenantDomain.toLowerCase(); + providerService = providerService.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, tenantDomain); + metric.increment(caller, tenantDomain); + Object timerMetric = metric.startTiming("gettenancy_timing", tenantDomain); + + // first verify that we have a valid tenant domain with policies + + Domain domain = dbService.getDomain(tenantDomain); + if (domain == null) { + throw ZMSUtils.notFoundError("getTenancy: No such tenant domain: " + tenantDomain, caller); + } + + // we need to contact the provider to retrieve tenancy details + // since we don't know if the provider supports resource groups + // and as such the policies we have are for tenant's subdomains + // or for tenant's domain with resource groups. + + String provSvcDomain = providerServiceDomain(providerService); + String provSvcName = providerServiceName(providerService); + + Domain providerDomain = dbService.getDomain(provSvcDomain); + if (providerDomain == null) { + throw ZMSUtils.requestError("getTenancy: No such provider domain: " + provSvcDomain, caller); + } + + // now retrieve our provider service object + + ServiceIdentity service = dbService.getServiceIdentity(provSvcDomain, provSvcName); + if (service == null) { + throw ZMSUtils.requestError("getTenancy: unable to retrieve service=" + providerService, caller); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("getTenancy: serviceIdentity: provider=" + service); + } + + // contact the provider and get the tenant object + + String url = service.getProviderEndpoint(); + if (url == null || url.isEmpty()) { + throw ZMSUtils.requestError("getTenancy: cannot get tenancy on provider service=" + + providerService + " -- not a provider service", caller); + } + + Principal tenantAdmin = ((RsrcCtxWrapper) ctx).principal(); + Tenant tenant = null; + try { + ProviderClient prov = getProviderClient(url, tenantAdmin); + tenant = prov.getTenant(provSvcName, tenantDomain); + } catch (ResourceException exc) { + // if we have a ZMS ResourceException then let's throw it + // as is so that the client knows that the provider returned + throw exc; + } catch (Exception exc) { + throw ZMSUtils.requestError("getTenancy: failed to get tenant on provider service(" + + providerService + "): " + exc.getMessage(), caller); + } + + if (tenant == null) { + throw ZMSUtils.notFoundError("getTenancy: Provider reports no such tenant: " + tenantDomain, caller); + } + + if (LOG.isInfoEnabled()) { + LOG.info("getTenancy: ---- result of provider.getTenant: " + tenant); + } + + // now we are going to verify to make sure that both tenant + // and provider domains have the appropriate policies. however we + // are not going to reject any requests because of missing policies + // and instead for resource group support we'll just not report + // the resource group as a valid provisioned one. + + Tenancy tenancy = new Tenancy(); + tenancy.setDomain(tenantDomain).setService(providerService); + List resourceGroups = tenant.getResourceGroups(); + if (resourceGroups != null) { + List tenantPolicies = dbService.listPolicies(tenantDomain); + Set providerPolicies = new HashSet<>(dbService.listPolicies(provSvcDomain)); + List tenancyResouceGroups = new ArrayList<>(); + for (String resourceGroup : resourceGroups) { + if (!verifyTenancyPolicies(tenantDomain, tenantPolicies, providerPolicies, + provSvcDomain, provSvcName, resourceGroup)) { + if (LOG.isInfoEnabled()) { + LOG.info("getTenancy: Invalid Resource Group: " + resourceGroup + + " for tenant: " + tenantDomain); + } + } else { + tenancyResouceGroups.add(resourceGroup); + } + } + tenancy.setResourceGroups(tenancyResouceGroups); + } + + metric.stopTiming(timerMetric); + return tenancy; + } + + public Tenancy deleteTenancy(ResourceContext ctx, String tenantDomain, String provider, String auditRef) { + + final String caller = "deletetenancy"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(provider, TYPE_SERVICE_NAME, caller); // fully qualified provider's service name + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + tenantDomain = tenantDomain.toLowerCase(); + provider = provider.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, tenantDomain); + metric.increment(caller, tenantDomain); + Object timerMetric = metric.startTiming("deletetenancy_timing", tenantDomain); + + // verify that request is properly authenticated for this request + + String authorizedService = ((RsrcCtxWrapper) ctx).principal().getAuthorizedService(); + verifyAuthorizedServiceOperation(authorizedService, caller); + + // for delete tenant operation we're going to go through the steps of + // lookup up provider's service object and make sure it has an endpoint + // configured and we can talk to it and request the tenant to be deleted + // if any of these operations fail, we're not going to reject the request + // but rather continue on and do the local cleanup. However, at the end + // we're going to return an exception with an error message stating exactly + // what failed so the administrator can go ahead and contact the provider + // manually, if necessary, to complete the delete tenancy process + + String errorMessage = null; + + // before local clean-up, we're going to contact the provider at their + // configured endpoint and request the tenant to be deleted. We need + // to do this before the local cleanup in ZMS because provider rdl + // has an authorize statement to validate that the specified domain + // is a valid tenant for the given provider. + + String provSvcDomain = providerServiceDomain(provider); + String provSvcName = providerServiceName(provider); + + // we are going to allow the authorize service token owner to call + // delete tenancy on its own service without configuring a controller + // end point + + boolean authzServiceTokenOperation = isAuthorizedProviderService(authorizedService, + provSvcDomain, provSvcName, tenantDomain, auditRef); + + // if this is an authorized service token operation there is no + // need to go through the provider check since the provider + // already handled that part + + if (authzServiceTokenOperation) { + + dbService.executeDeleteTenantRoles(ctx, provSvcDomain, provSvcName, tenantDomain, null, + auditRef, caller); + + } else { + + ServiceIdentity provSvcId = dbService.getServiceIdentity(provSvcDomain, provSvcName); + if (provSvcId == null) { + errorMessage = "service does not exist"; + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("provider serviceIdentity(" + provSvcId + ")"); + } + + String url = provSvcId.getProviderEndpoint(); + if (url == null || url.isEmpty()) { + errorMessage = "service does not have endpoint configured"; + } else { + if (LOG.isInfoEnabled()) { + LOG.info("Tenant will contact provider at endpoint: " + url); + } + + try { + Principal tenantAdmin = ((RsrcCtxWrapper) ctx).principal(); + ProviderClient prov = getProviderClient(url, tenantAdmin); + prov.deleteTenant(provSvcName, tenantDomain, auditRef); + } catch (Exception exc) { + errorMessage = "failed to delete tenant. Error: " + exc.getMessage(); + } + } + } + } + + // now clean-up local domain roles and policies for this tenant + + dbService.executeDeleteTenancy(ctx, tenantDomain, provSvcDomain, provSvcName, + null, auditRef, caller); + + metric.stopTiming(timerMetric); + + // so if we have an error message then we're going to throw an exception + // otherwise the operation was completed successfully + + if (errorMessage != null) { + final String tenantCleanupMsg = "deleteTenancy: Tenant cleanup in(" + tenantDomain + "): "; + throw ZMSUtils.requestError(tenantCleanupMsg + "completed successfully. However, there " + + "was an error when contacting the Provider Service: " + provider + ":" + + errorMessage + ". Please contact the Provider administrator directly " + + "to complete this delete tenancy request", caller); + } + + } catch (Exception exc) { + + auditRequestFailure(ctx, exc, tenantDomain, provider, caller, ZMSConsts.HTTP_DELETE, null, auditRef); + throw exc; + } + + return null; + } + + public TenancyResourceGroup deleteTenancyResourceGroup(ResourceContext ctx, String tenantDomain, + String provider, String resourceGroup, String auditRef) { + + final String caller = "deletetenancyresourcegroup"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(provider, TYPE_SERVICE_NAME, caller); // fully qualified provider's service name + validate(resourceGroup, TYPE_COMPOUND_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + tenantDomain = tenantDomain.toLowerCase(); + provider = provider.toLowerCase(); + resourceGroup = resourceGroup.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, tenantDomain); + metric.increment(caller, tenantDomain); + Object timerMetric = metric.startTiming("deletetenancyresourcegroup_timing", tenantDomain); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + // for delete tenant resource group operation we're going to go through + // the steps of lookup up provider's service object and make sure it has + // an endpoint configured and we can talk to it and request the tenant + // resource group to be deleted. if any of these operations fail, we're not + // going to reject the request but rather continue on and do the local cleanup. + // However, at the end we're going to return an exception with an error message + // stating exactly what failed so the administrator can go ahead and contact + // the provider manually, if necessary, to complete the delete tenancy + // resource group process + + String errorMessage = null; + + // before local clean-up, we're going to contact the provider at their + // configured endpoint and request the tenant resource group to be deleted. + + String provSvcDomain = providerServiceDomain(provider); + String provSvcName = providerServiceName(provider); + + ServiceIdentity provSvcId = dbService.getServiceIdentity(provSvcDomain, provSvcName); + if (provSvcId == null) { + errorMessage = "service does not exist"; + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("provider serviceIdentity(" + provSvcId + ")"); + } + + String url = provSvcId.getProviderEndpoint(); + if (url == null) { + errorMessage = "service does not have endpoint configured"; + } else { + if (LOG.isInfoEnabled()) { + LOG.info("Tenant will contact provider at endpoint: " + url); + } + + try { + Principal tenantAdmin = ((RsrcCtxWrapper) ctx).principal(); + ProviderClient prov = getProviderClient(url, tenantAdmin); + prov.deleteTenantResourceGroup(provSvcName, tenantDomain, resourceGroup, auditRef); + } catch (Exception exc) { + errorMessage = "failed to delete tenant resource group. Error: " + exc.getMessage(); + } + } + } + + // now clean-up local domain roles and policies for this tenant + + dbService.executeDeleteTenancy(ctx, tenantDomain, provSvcDomain, provSvcName, + resourceGroup, auditRef, caller); + + metric.stopTiming(timerMetric); + + // so if we have an error message then we're going to throw an exception + // otherwise the operation was completed successfully + + if (errorMessage != null) { + final String tenantCleanupMsg = "Tenant cleanup in(" + tenantDomain + "): "; + throw ZMSUtils.requestError(tenantCleanupMsg + "completed successfully. However, there " + + "was an error when contacting the Provider Service: " + provider + ":" + + errorMessage + ". Please contact the Provider administrator directly " + + "to complete this delete tenancy resource group request", caller); + } + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified resource-group=(").append(resourceGroup).append(')'); + auditRequestFailure(ctx, exc, tenantDomain, provider, caller, ZMSConsts.HTTP_DELETE, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + long sleepBeforeRetryingRequest(long millisToExpire, long sleepTimeout, String caller) { + + // before sleeping we're going to check to see if it makes + // sense since if the while loop is going to break out then + // there is no point of us sleeping now + + if (millisToExpire <= 0) { + return sleepTimeout; + } + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": concurrent update exception, retry after " + sleepTimeout + "ms"); + } + + try { + Thread.sleep(sleepTimeout); + } catch (InterruptedException e) { + } + + // since we know we're going to retry our operation next + // lets increment our domain update retry counter + + metric.increment("domainupdateretry"); + + // we're going to sleep a bit longer after each iteration + // so our next timeout is twice as long + + return (2 * sleepTimeout); + } + + public TenantRoles putTenantRoles(ResourceContext ctx, String provSvcDomain, String provSvcName, + String tenantDomain, String auditRef, TenantRoles detail) { + + final String caller = "puttenantroles"; + metric.increment(ZMSConsts.HTTP_PUT); + + try { + validate(provSvcDomain, TYPE_DOMAIN_NAME, caller); + validate(provSvcName, TYPE_SIMPLE_NAME, caller); //not including the domain, this is the domain's service + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(detail, TYPE_TENANT_ROLES, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + provSvcDomain = provSvcDomain.toLowerCase(); + provSvcName = provSvcName.toLowerCase(); + tenantDomain = tenantDomain.toLowerCase(); + AthenzObject.TENANT_ROLES.convertToLowerCase(detail); + + metric.increment(ZMSConsts.HTTP_REQUEST, provSvcDomain); + metric.increment(caller, provSvcDomain); + Object timerMetric = metric.startTiming("puttenantroles_timing", provSvcDomain); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + if (LOG.isInfoEnabled()) { + LOG.info("putTenantRoles: ==== putTenantRoles(domain=" + provSvcDomain + ", service=" + + provSvcName + ", tenant-domain=" + tenantDomain + ", detail=" + detail + ")"); + } + + List roles = detail.getRoles(); + if (roles == null || roles.size() == 0) { + throw ZMSUtils.requestError("putTenantRoles: must include at least one role", caller); + } + + dbService.executePutTenantRoles(ctx, provSvcDomain, provSvcName, tenantDomain, null, + roles, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified provider-service=(").append(provSvcName).append(')'); + auditRequestFailure(ctx, exc, provSvcDomain, tenantDomain, caller, ZMSConsts.HTTP_PUT, + sb.toString(), auditRef); + throw exc; + } + + return detail; + } + + // put the trust roles into provider domain + // + public TenantResourceGroupRoles putTenantResourceGroupRoles(ResourceContext ctx, String provSvcDomain, + String provSvcName, String tenantDomain, String resourceGroup, String auditRef, + TenantResourceGroupRoles detail) { + + final String caller = "puttenantresourcegrouproles"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(provSvcDomain, TYPE_DOMAIN_NAME, caller); + validate(provSvcName, TYPE_SIMPLE_NAME, caller); //not including the domain, this is the domain's service + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(detail, TYPE_TENANT_RESOURCE_GROUP_ROLES, caller); + validate(resourceGroup, TYPE_COMPOUND_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + provSvcDomain = provSvcDomain.toLowerCase(); + provSvcName = provSvcName.toLowerCase(); + tenantDomain = tenantDomain.toLowerCase(); + resourceGroup = resourceGroup.toLowerCase(); + AthenzObject.TENANT_RESOURCE_GROUP_ROLES.convertToLowerCase(detail); + + metric.increment(ZMSConsts.HTTP_REQUEST, provSvcDomain); + metric.increment(caller, provSvcDomain); + Object timerMetric = metric.startTiming("puttenantresourcegrouproles_timing", provSvcDomain); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + if (LOG.isInfoEnabled()) { + LOG.info("putTenantResourceGroupRoles: ==== putTenantRoles(domain=" + provSvcDomain + ", service=" + + provSvcName + ", tenant-domain=" + tenantDomain + ", resource-group=" + resourceGroup + + ", detail=" + detail + ")"); + } + + List roles = detail.getRoles(); + if (roles == null || roles.size() == 0) { + throw ZMSUtils.requestError("putTenantResourceGroupRoles: must include at least one role", caller); + } + + dbService.executePutTenantRoles(ctx, provSvcDomain, provSvcName, tenantDomain, + resourceGroup, roles, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified resource-group=(").append(resourceGroup).append(')'); + auditRequestFailure(ctx, exc, provSvcDomain, tenantDomain, caller, ZMSConsts.HTTP_PUT, + sb.toString(), auditRef); + throw exc; + } + + return detail; + } + + public DomainDataCheck getDomainDataCheck(ResourceContext ctx, String domainName) { + + final String caller = "getdomaindatacheck"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + domainName = domainName.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + Object timerMetric = metric.startTiming("getdomaindatacheck_timing", domainName); + + if (LOG.isDebugEnabled()) { + LOG.debug("getDomainDataCheck: domain=" + domainName); + } + + AthenzDomain domain = getAthenzDomain(domainName, false); + if (domain == null) { + throw ZMSUtils.notFoundError("getDomainDataCheck: Domain not found: '" + domainName + "'", caller); + } + + // build set of roles + // iterate them to look for trust roles - in case this is a provider domain + + Set roleSet = new HashSet<>(); + Set trustRoleSet = new HashSet<>(); + + // map per trust/tenant domain that contains the trust roles + + Map> trustRoleMap = new HashMap>(); + for (Role role : domain.getRoles()) { + if (LOG.isDebugEnabled()) { + LOG.debug("getDomainDataCheck: processing role - " + role.getName()); + } + roleSet.add(role.getName()); + String roleName = ZMSUtils.removeDomainPrefix(role.getName(), domainName, ROLE_PREFIX); + String trustDomain = role.getTrust(); + if (trustDomain != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("trust role for domain: " + trustDomain); + } + trustRoleSet.add(trustDomain); + Set tset = trustRoleMap.get(trustDomain); + if (tset == null) { + tset = new HashSet(); + trustRoleMap.put(trustDomain, tset); + } + tset.add(roleName); + } + } + + // look for dangling roles and policies + // + int assertionCount = 0; + int roleWildcardCount = 0; + Set usedRoleSet = new HashSet<>(); // keep track of roles used by policies + Set providerSet = new HashSet<>(); // keep track of providers from assume_role policies + + // assume_role resources are placed into the set per provider service domain + + Map> svcRoleMap = new HashMap>(); + List danglingPolicies = new ArrayList<>(); + List policies = domain.getPolicies(); + for (Policy policy : policies) { + String pname = ZMSUtils.removeDomainPrefix(policy.getName(), domainName, POLICY_PREFIX); + if (LOG.isDebugEnabled()) { + LOG.debug("getDomainDataCheck: processing policy=" + pname + " in domain=" + domainName); + } + + List assertions = policy.getAssertions(); + if (assertions == null) { + continue; + } + + for (Assertion assertion : assertions) { + assertionCount++; + if (ZMSConsts.ACTION_ASSUME_ROLE.equalsIgnoreCase(assertion.getAction())) { + // get provider domain+service name and add to set of providers + // Note there may be a resource appended - to be dealt with later + // ex: testgetdomaindatacheck:policy.tenancy.testgetdomaindatacheckprovider.storage.reader + // ex: testgetdomaindatacheck:policy.tenancy.testgetdomaindatacheckprovider.sub.storage.res_group.ravers.reader + // index after "tenancy." and index of last dot + int index = pname.indexOf("tenancy."); + if (index == -1) { + continue; + } + int lindex = pname.lastIndexOf('.'); + if (lindex == -1) { + continue; + } + String provSvcDomain = pname.substring(index + "tenancy.".length(), lindex); + providerSet.add(provSvcDomain); + + // lets collect the resource field that is name of role in provider + // ex: testgetdomaindatacheckprovider.sub:role.storage.tenant.testgetdomaindatacheck.reader + // ex: testgetdomaindatacheckprovider.sub:role.storage.tenant.testgetdomaindatacheck.res_group.ravers.reader + String rsrc = assertion.getResource(); + Set rset = svcRoleMap.get(provSvcDomain); + if (rset == null) { + rset = new HashSet(); + svcRoleMap.put(provSvcDomain, rset); + } + rset.add(rsrc); + } + + String roleName = assertion.getRole(); + + // check for wildcard role + if (roleName.lastIndexOf('*') != -1) { + roleWildcardCount++; + // make sure there is at least 1 role that can match + // this wildcard - else its a dangling policy + String rolePattern = StringUtils.patternFromGlob(roleName); + boolean wildCardMatch = false; + for (String role: roleSet) { + if (role.matches(rolePattern)) { + wildCardMatch = true; + break; + } + } + if (wildCardMatch == false) { // dangling policy + DanglingPolicy dp = new DanglingPolicy(); + // we need to remove the domain:role. and domain:policy prefixes + // according to RDL definitions for role and policy names + dp.setRoleName(ZMSUtils.removeDomainPrefix(roleName, domainName, ROLE_PREFIX)); + dp.setPolicyName(ZMSUtils.removeDomainPrefix(pname, domainName, POLICY_PREFIX)); + danglingPolicies.add(dp); + } + } else if (roleSet.contains(roleName)) { + usedRoleSet.add(roleName); + } else { // dangling policy + DanglingPolicy dp = new DanglingPolicy(); + // we need to remove the domain:role. and domain:policy prefixes + // according to RDL definitions for role and policy names + dp.setRoleName(ZMSUtils.removeDomainPrefix(roleName, domainName, ROLE_PREFIX)); + dp.setPolicyName(ZMSUtils.removeDomainPrefix(pname, domainName, POLICY_PREFIX)); + danglingPolicies.add(dp); + } + } + } + + DomainDataCheck ddc = new DomainDataCheck(); + ddc.setPolicyCount(policies.size()); + ddc.setAssertionCount(assertionCount); + ddc.setRoleWildCardCount(roleWildcardCount); + if (!danglingPolicies.isEmpty()) { + ddc.setDanglingPolicies(danglingPolicies); + } + + if (roleSet.size() != usedRoleSet.size()) { + // ohoh, some roles are unused - need to subtract the usedRoleSet + // from roleSet - the leftovers are the unused roles + roleSet.removeAll(usedRoleSet); + // we need to remove the domain:role. prefix according to + // RDL definition for dangling role names + List danglingRoleList = new ArrayList(); + for (String roleName : roleSet) { + danglingRoleList.add(ZMSUtils.removeDomainPrefix(roleName, domainName, ROLE_PREFIX)); + } + ddc.setDanglingRoles(danglingRoleList); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("getDomainDataCheck: domain=" + domainName + + " policy-count=" + policies.size() + " assertion-count=" + + assertionCount + " wildcard-count==" + roleWildcardCount + + " dangling-policies=" + danglingPolicies.size() + + " dangling-roles=" + roleSet.size()); + } + + // Tenant Domain Check: does each provider fully support this tenant? + // collect Service names (domain.service) for domains that don't contain + // trust role + List provsWithoutTrust = new ArrayList<>(); + for (String provSvc : providerSet) { + if (LOG.isDebugEnabled()) { + LOG.debug("getDomainDataCheck: domain=" + domainName + + " provider-service=" + provSvc); + } + + // 2 cases to resolve, one with resource group, one without + // ex: iaas.stuff.storage.read + // ex: iaas.stuff.storage.res_group.my_resource_group.read + + int idx = provSvc.indexOf(".res_group."); + String provSvcDomain = null; + if (idx == -1) { + provSvcDomain = providerServiceDomain(provSvc); + } else { + provSvcDomain = providerServiceDomain(provSvc.substring(0, idx)); + } + + AthenzDomain providerDomain = getAthenzDomain(provSvcDomain, true); + Set rset = svcRoleMap.get(provSvc); + if (rset == null || rset.isEmpty() || providerDomain == null) { + provsWithoutTrust.add(provSvc); + continue; + } + + // find trust role in the provider that contains the tenant domain + int foundTrust = 0; + for (Role role : providerDomain.getRoles()) { + String trustDomain = role.getTrust(); + if (trustDomain != null) { + if (domainName.equals(trustDomain)) { + // is this role a match for an assume role in the tenant + // look for the role in the role set for this service + if (rset.contains(role.getName())) { + foundTrust++; + } + } + } + } + if (foundTrust != rset.size()) { + provsWithoutTrust.add(provSvc); + } + } + if (!provsWithoutTrust.isEmpty()) { + ddc.setProvidersWithoutTrust(provsWithoutTrust); + } + + // Provider Domain Check: does each tenant have all the assume_role + // assertions to match each trust role. + + // tenantsWithoutProv: names of Tenant domains that dont contain assume + // role assertions if this is a provider domain + List tenantsWithoutProv = new ArrayList<>(); + + // tenantDomMap: optimize reading tenant domains once already read + // This is optimizing for Providers with lots of tenants. + Map tenantDomMap = new HashMap<>(); + for (String trustRole: trustRoleSet) { + + if (LOG.isDebugEnabled()) { + LOG.debug("getDomainDataCheck: processing trust role: " + trustRole); + } + + AthenzDomain tenantDomain = tenantDomMap.get(trustRole); + if (tenantDomain == null) { + tenantDomain = getAthenzDomain(trustRole, true); + if (tenantDomain == null) { + tenantsWithoutProv.add(trustRole); + continue; + } else { + tenantDomMap.put(trustRole, tenantDomain); + } + } + + // Get set of providers trust roles for trust/tenant domain. + Set tset = trustRoleMap.get(trustRole); + if (tset == null || tset.isEmpty()) { + tenantsWithoutProv.add(trustRole); + continue; + } + + int foundProviderCnt = 0; + + // Check for assume_role containing the provider in the tenantDomain + for (Policy policy : tenantDomain.getPolicies()) { + List assertions = policy.getAssertions(); + if (assertions == null) { + continue; + } + for (Assertion assertion : assertions) { + if (ZMSConsts.ACTION_ASSUME_ROLE.equalsIgnoreCase(assertion.getAction())) { + String rsrc = assertion.getResource(); + // If the provider domain contains a role that matches + // the tenant domain resource - then the tenant is supported + if (roleSet.contains(rsrc)) { + // HAVE: an assume_role with resource pointing at the provider + foundProviderCnt++; + } + } + } + } + if (foundProviderCnt < tset.size()) { + // didnt find all required matching provider trust-role to assume_role-resource pairs + tenantsWithoutProv.add(trustRole); + } + } + if (!tenantsWithoutProv.isEmpty()) { + ddc.setTenantsWithoutAssumeRole(tenantsWithoutProv); + } + + metric.stopTiming(timerMetric); + return ddc; + } + + public ProviderResourceGroupRoles deleteProviderResourceGroupRoles(ResourceContext ctx, String tenantDomain, + String provSvcDomain, String provSvcName, String resourceGroup, String auditRef) { + + final String caller = "deleteproviderresourcegrouproles"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(provSvcDomain, TYPE_DOMAIN_NAME, caller); + validate(provSvcName, TYPE_SIMPLE_NAME, caller); + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(resourceGroup, TYPE_COMPOUND_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + provSvcDomain = provSvcDomain.toLowerCase(); + provSvcName = provSvcName.toLowerCase(); + tenantDomain = tenantDomain.toLowerCase(); + resourceGroup = resourceGroup.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, provSvcDomain); + metric.increment(caller, provSvcDomain); + Object timerMetric = metric.startTiming("deleteproviderresourcegrouproles_timing", provSvcDomain); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + // first clean-up local domain roles and policies for this tenant + + dbService.executeDeleteTenancy(ctx, tenantDomain, provSvcDomain, provSvcName, + resourceGroup, auditRef, caller); + + // at this point the tenant side is complete. If the token was a chained + // token signed by the provider service then we're going to process the + // provider side as well thus complete the tenancy delete process + + String authorizedService = ((RsrcCtxWrapper) ctx).principal().getAuthorizedService(); + if (isAuthorizedProviderService(authorizedService, provSvcDomain, provSvcName, + tenantDomain, auditRef)) { + + dbService.executeDeleteTenantRoles(ctx, provSvcDomain, provSvcName, tenantDomain, + resourceGroup, auditRef, caller); + } + + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified provider-service=(").append(provSvcName) + .append(");resource-group=(").append(resourceGroup).append(")"); + auditRequestFailure(ctx, exc, tenantDomain, provSvcDomain, caller, ZMSConsts.HTTP_DELETE, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + public ProviderResourceGroupRoles getProviderResourceGroupRoles(ResourceContext ctx, String tenantDomain, + String provSvcDomain, String provSvcName, String resourceGroup) { + + final String caller = "getproviderresourcegrouproles"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(provSvcDomain, TYPE_DOMAIN_NAME, caller); + validate(provSvcName, TYPE_SIMPLE_NAME, caller); + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(resourceGroup, TYPE_COMPOUND_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + provSvcDomain = provSvcDomain.toLowerCase(); + provSvcName = provSvcName.toLowerCase(); + tenantDomain = tenantDomain.toLowerCase(); + resourceGroup = resourceGroup.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, provSvcDomain); + metric.increment(caller, provSvcDomain); + Object timerMetric = metric.startTiming("getproviderresourcegrouproles_timing", provSvcDomain); + + Domain domain = dbService.getDomain(tenantDomain); + if (domain == null) { + throw ZMSUtils.notFoundError("No such domain: " + tenantDomain, caller); + } + + // look for this provider roles, ex: storage.tenant.sports.reader + + String rolePrefix = ZMSUtils.getProviderResourceGroupRolePrefix(provSvcDomain, provSvcName, resourceGroup); + ProviderResourceGroupRoles provRoles = new ProviderResourceGroupRoles().setDomain(provSvcDomain) + .setService(provSvcName).setTenant(tenantDomain).setResourceGroup(resourceGroup); + + List tralist = new ArrayList(); + + // find roles matching the prefix + + List rcollection = dbService.listRoles(tenantDomain); + for (String rname: rcollection) { + + if (dbService.isTenantRolePrefixMatch(rname, rolePrefix, null)) { + + // for provider roles we don't have the action, that's + // for the provider domain only so we're just going + // to return the list of roles without any actions + // for the role name we must return the SimpleName + // part only so we'll remove the prefix section + + TenantRoleAction tra = new TenantRoleAction() + .setRole(rname.substring(rolePrefix.length())) + .setAction("n/a"); + tralist.add(tra); + } + } + provRoles.setRoles(tralist); + + metric.stopTiming(timerMetric); + return provRoles; + } + + boolean isAuthorizedProviderService(String authorizedService, String provSvcDomain, + String provSvcName, String tenantDomain, String auditRef) { + + // make sure we have a service provided and it matches to our provider + + if (authorizedService == null) { + return false; + } + + if (!authorizedService.equals(provSvcDomain + "." + provSvcName)) { + return false; + } + + // verify that provider service does indeed have access to provision + // its own tenants. the authorize statement for the putTenantRole + // command is defined in the RDL as: + // authorize ("UPDATE", "{domain}:tenant.{tenantDomain}"); + + AthenzDomain domain = getAthenzDomain(provSvcDomain, true); + if (domain == null) { + return false; + } + + // evaluate our domain's roles and policies to see if access + // is allowed or not for the given operation and resource + + String resource = provSvcDomain + ":tenant." + tenantDomain; + AccessStatus accessStatus = evaluateAccess(domain, authorizedService, "update", + resource, null, null); + + if (accessStatus == AccessStatus.ALLOWED) { + return true; + } else { + return false; + } + } + + /** + * This sets up the assume roles in the tenant. If the tenants admin user + * token has been authorized by the provider, the providers domain will be + * updated as well, thus completing the tenancy on-boarding in a single step. + **/ + public ProviderResourceGroupRoles putProviderResourceGroupRoles(ResourceContext ctx, String tenantDomain, + String provSvcDomain, String provSvcName, String resourceGroup, String auditRef, + ProviderResourceGroupRoles detail) { + + final String caller = "putproviderresourcegrouproles"; + metric.increment(ZMSConsts.HTTP_PUT); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(provSvcDomain, TYPE_DOMAIN_NAME, caller); + validate(provSvcName, TYPE_SIMPLE_NAME, caller); //not including the domain, this is the domain's service + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(detail, TYPE_PROVIDER_RESOURCE_GROUP_ROLES, caller); + validate(resourceGroup, TYPE_COMPOUND_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + provSvcDomain = provSvcDomain.toLowerCase(); + provSvcName = provSvcName.toLowerCase(); + tenantDomain = tenantDomain.toLowerCase(); + resourceGroup = resourceGroup.toLowerCase(); + AthenzObject.PROVIDER_RESOURCE_GROUP_ROLES.convertToLowerCase(detail); + + metric.increment(ZMSConsts.HTTP_REQUEST, provSvcDomain); + metric.increment(caller, provSvcDomain); + Object timerMetric = metric.startTiming("putproviderresourcegrouproles_timing", provSvcDomain); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + if (LOG.isInfoEnabled()) { + LOG.info("putProviderResourceGroupRoles: domain=" + provSvcDomain + ", service=" + + provSvcName + ", tenant-domain=" + tenantDomain + ", resource-group=" + resourceGroup + + ", detail=" + detail); + } + + // set up our tenant admin policy so provider can check admin's access + + dbService.setupTenantAdminPolicy(ctx, tenantDomain, provSvcDomain, provSvcName, auditRef, caller); + + // now we're going to setup our roles + + List roleActions = detail.getRoles(); + if (roleActions == null || roleActions.size() == 0) { + throw ZMSUtils.requestError("putProviderResourceGroupRoles: must include at least one role", caller); + } + + List roles = new ArrayList<>(); + for (TenantRoleAction roleAction : roleActions) { + roles.add(roleAction.getRole()); + } + + // we're going to create a separate role for each one of tenant roles returned + // based on its action and set the caller as a member in each role + + dbService.executePutProviderRoles(ctx, tenantDomain, provSvcDomain, provSvcName, resourceGroup, + roles, auditRef, caller); + + // at this point the tenant side is complete. If the token was a chained + // token signed by the provider service then we're going to process the + // provider side as well thus complete the tenancy on-boarding process + + String authorizedService = ((RsrcCtxWrapper) ctx).principal().getAuthorizedService(); + if (isAuthorizedProviderService(authorizedService, provSvcDomain, provSvcName, + tenantDomain, auditRef)) { + + dbService.executePutTenantRoles(ctx, provSvcDomain, provSvcName, tenantDomain, + resourceGroup, roleActions, auditRef, caller); + } + + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified provider-service=(").append(provSvcName) + .append(");resource-group=(").append(resourceGroup).append(")"); + auditRequestFailure(ctx, exc, tenantDomain, provSvcDomain, caller, ZMSConsts.HTTP_PUT, + sb.toString(), auditRef); + throw exc; + } + + return detail; + } + + String getProviderRoleAction(String provSvcDomain, String roleName) { + + // if no match then we're going to default action of empty string + + Policy policy = dbService.getPolicy(provSvcDomain, roleName); // policy has same name + if (policy == null) { + return ""; + } + + List assertions = policy.getAssertions(); + if (assertions == null) { + return ""; + } + + for (Assertion assertion : assertions) { + if (!assertion.getRole().endsWith(roleName)) { + continue; + } + + return assertion.getAction(); + } + + return ""; + } + + public TenantRoles getTenantRoles(ResourceContext ctx, String provSvcDomain, String provSvcName, + String tenantDomain) { + + final String caller = "gettenantroles"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(provSvcDomain, TYPE_DOMAIN_NAME, caller); + validate(provSvcName, TYPE_SIMPLE_NAME, caller); // not including the domain, this is the domain's service type + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + provSvcDomain = provSvcDomain.toLowerCase(); + provSvcName = provSvcName.toLowerCase(); + tenantDomain = tenantDomain.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, provSvcDomain); + metric.increment(caller, provSvcDomain); + Object timerMetric = metric.startTiming("gettenantroles_timing", provSvcDomain); + + // look for this tenants roles, ex: storage.tenant.sports.reader + String rolePrefix = ZMSUtils.getTenantResourceGroupRolePrefix(provSvcName, tenantDomain, null); + TenantRoles troles = new TenantRoles().setDomain(provSvcDomain).setService(provSvcName) + .setTenant(tenantDomain); + + Domain domain = dbService.getDomain(provSvcDomain); + if (domain == null) { + throw ZMSUtils.notFoundError("getTenantRoles: No such domain: " + provSvcDomain, caller); + } + + List tralist = new ArrayList(); + + // find roles matching the prefix + List rcollection = dbService.listRoles(provSvcDomain); + for (String rname: rcollection) { + if (dbService.isTrustRoleForTenant(provSvcDomain, rname, rolePrefix, tenantDomain)) { + // good, its exactly what we are looking for, but + // now we want the ACTION that was set in the provider + + String action = getProviderRoleAction(provSvcDomain, rname); + + // for the role name we must return the SimpleName + // part only so we'll remove the prefix section + + TenantRoleAction tra = new TenantRoleAction() + .setRole(rname.substring(rolePrefix.length())) + .setAction(action); + tralist.add(tra); + } + } + troles.setRoles(tralist); + + metric.stopTiming(timerMetric); + return troles; + } + + public TenantResourceGroupRoles getTenantResourceGroupRoles(ResourceContext ctx, String provSvcDomain, + String provSvcName, String tenantDomain, String resourceGroup) { + + final String caller = "gettenantresourcegrouproles"; + metric.increment(ZMSConsts.HTTP_GET); + + validate(provSvcDomain, TYPE_DOMAIN_NAME, caller); + validate(provSvcName, TYPE_SIMPLE_NAME, caller); // not including the domain, this is the domain's service type + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(resourceGroup, TYPE_COMPOUND_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + provSvcDomain = provSvcDomain.toLowerCase(); + provSvcName = provSvcName.toLowerCase(); + tenantDomain = tenantDomain.toLowerCase(); + resourceGroup = resourceGroup.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, provSvcDomain); + metric.increment(caller, provSvcDomain); + Object timerMetric = metric.startTiming("gettenantresourcegrouproles_timing", provSvcDomain); + + // look for this tenants roles, ex: storage.tenant.sports.reader + + String rolePrefix = ZMSUtils.getTenantResourceGroupRolePrefix(provSvcName, tenantDomain, resourceGroup); + TenantResourceGroupRoles troles = new TenantResourceGroupRoles().setDomain(provSvcDomain) + .setService(provSvcName).setTenant(tenantDomain).setResourceGroup(resourceGroup); + + Domain domain = dbService.getDomain(provSvcDomain); + if (domain == null) { + throw ZMSUtils.notFoundError("getTenantResourceGroupRoles: No such domain: " + provSvcDomain, caller); + } + + List tralist = new ArrayList(); + + // find roles matching the prefix + + List rcollection = dbService.listRoles(provSvcDomain); + for (String rname: rcollection) { + if (dbService.isTrustRoleForTenant(provSvcDomain, rname, rolePrefix, tenantDomain)) { + + // good, its exactly what we are looking for, but + // now we want the ACTION that was set in the provider + + String action = getProviderRoleAction(provSvcDomain, rname); + + // for the role name we must return the SimpleName + // part only so we'll remove the prefix section + + TenantRoleAction tra = new TenantRoleAction() + .setRole(rname.substring(rolePrefix.length())) + .setAction(action); + tralist.add(tra); + } + } + troles.setRoles(tralist); + + metric.stopTiming(timerMetric); + return troles; + } + + public TenantRoles deleteTenantRoles(ResourceContext ctx, String provSvcDomain, String provSvcName, + String tenantDomain, String auditRef) { + + final String caller = "deletetenantroles"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(provSvcDomain, TYPE_DOMAIN_NAME, caller); + validate(provSvcName, TYPE_SIMPLE_NAME, caller); // not including the domain, this is the domain's service type + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + provSvcDomain = provSvcDomain.toLowerCase(); + provSvcName = provSvcName.toLowerCase(); + tenantDomain = tenantDomain.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, provSvcDomain); + metric.increment(caller, provSvcDomain); + Object timerMetric = metric.startTiming("deletetenantroles_timing", provSvcDomain); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + dbService.executeDeleteTenantRoles(ctx, provSvcDomain, provSvcName, tenantDomain, + null, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified provider-service=(").append(provSvcName).append(')'); + auditRequestFailure(ctx, exc, provSvcDomain, tenantDomain, caller, ZMSConsts.HTTP_DELETE, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + public TenantResourceGroupRoles deleteTenantResourceGroupRoles(ResourceContext ctx, String provSvcDomain, + String provSvcName, String tenantDomain, String resourceGroup, String auditRef) { + + final String caller = "deletetenantresourcegrouproles"; + metric.increment(ZMSConsts.HTTP_DELETE); + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(provSvcDomain, TYPE_DOMAIN_NAME, caller); + validate(provSvcName, TYPE_SIMPLE_NAME, caller); // not including the domain, this is the domain's service type + validate(tenantDomain, TYPE_DOMAIN_NAME, caller); + validate(resourceGroup, TYPE_COMPOUND_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + provSvcDomain = provSvcDomain.toLowerCase(); + provSvcName = provSvcName.toLowerCase(); + tenantDomain = tenantDomain.toLowerCase(); + resourceGroup = resourceGroup.toLowerCase(); + + metric.increment(ZMSConsts.HTTP_REQUEST, provSvcDomain); + metric.increment(caller, provSvcDomain); + Object timerMetric = metric.startTiming("deletetenantresourcegrouproles_timing", provSvcDomain); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + dbService.executeDeleteTenantRoles(ctx, provSvcDomain, provSvcName, tenantDomain, + resourceGroup, auditRef, caller); + metric.stopTiming(timerMetric); + + } catch (Exception exc) { + + StringBuilder sb = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + sb.append(":caller specified provider-service=(").append(provSvcName).append(')'); + auditRequestFailure(ctx, exc, provSvcDomain, tenantDomain, caller, ZMSConsts.HTTP_DELETE, + sb.toString(), auditRef); + throw exc; + } + + return null; + } + + String yrnDomain(String yrn) { + //"yrn:service:location:domain:entity" or "domain:entity" + String [] s = yrn.split(":"); + if (s.length <= 2) { + return s[0]; + } else if (s.length == 5) { + return s[3]; + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("yrnDomain: missing domain name: " + yrn); + } + return null; + } + } + + void validate(Object val, String type, String caller) { + if (val == null) { + throw ZMSUtils.requestError("Missing or malformed " + type, caller); + } + + Result result = validator.validate(val, type); + if (!result.valid) { + throw ZMSUtils.requestError("Invalid " + type + " error: " + result.error, caller); + } + } + + List validatedAdminUsers(List lst) { + + final String caller = "validatedadminusers"; + + if (lst == null || lst.size() == 0) { + throw ZMSUtils.requestError("validatedAdminUsers: Missing adminUsers", caller); + } + Set users = new HashSet<>(); + for (String user : lst) { + validate(user, TYPE_RESOURCE_NAME, caller); + users.add(user); + } + return new ArrayList(users); + } + + Domain createTopLevelDomain(ResourceContext ctx, String domainName, String description, + String org, Boolean auditEnabled, List adminUsers, String account, + int productId, List solutionTemplates, String auditRef) { + List users = validatedAdminUsers(adminUsers); + return dbService.makeDomain(ctx, domainName, description, org, auditEnabled, + users, account, productId, solutionTemplates, auditRef); + } + + Domain createSubDomain(ResourceContext ctx, String parentName, String name, String description, + String org, Boolean auditEnabled, List adminUsers, String account, + int productId, List solutionTemplates, String auditRef, String caller) { + + // verify length of full sub domain name + String fullSubDomName = parentName + "." + name; + if (fullSubDomName.length() > domainNameMaxLen) { + throw ZMSUtils.requestError("Invalid SubDomain name: " + fullSubDomName + " : name length cannot exceed: " + domainNameMaxLen, caller); + } + + List users = validatedAdminUsers(adminUsers); + return dbService.makeDomain(ctx, fullSubDomName, description, org, auditEnabled, + users, account, productId, solutionTemplates, auditRef); + } + + int countDots(String str) { + int count = 0; + int i = str.indexOf('.'); + while (i >= 0) { + count++; + i = str.indexOf('.', i + 1); + } + return count; + } + + boolean hasExceededDepthLimit(Integer depth, String name) { + + if (depth == null) { + return false; + } + + // depth=0 means only top level + + if (countDots(name) > depth) { + return true; + } else { + return false; + } + } + + DomainList listDomains(Integer limit, String skip, String prefix, Integer depth, long modTime) { + + //note: we don't use the store's options, because we also need to filter on depth + + List allDomains = dbService.listDomains(prefix, modTime); + List names = new ArrayList(); + + for (String name : allDomains) { + if (hasExceededDepthLimit(depth, name)) { + continue; + } + names.add(name); + } + + int count = names.size(); + if (skip != null) { + for (int i = 0; i < count; i++) { + String name = names.get(i); + if (skip.equals(name)) { + names = names.subList(i + 1, count); + count = names.size(); + break; + } + } + } + + DomainList result = new DomainList(); + + // if we have exceeded our requested list then + // set the next skip entry in our result + + if (hasExceededListLimit(limit, count)) { + names = names.subList(0, limit); + result.setNext(names.get(limit - 1)); + } + + result.setNames(names); + return result; + } + + boolean isZMSService(String domain, String service) { + return (SYS_AUTH.equalsIgnoreCase(domain) && ZMS.equalsIgnoreCase(service)); + } + + /** + * implements KeyStore getPublicKey + * @return String with PEM encoded key, which should be ybase64decoded prior + * to return if ybase64encoded + **/ + @Override + public String getPublicKey(String domain, String service, String keyId) { + + if (LOG.isDebugEnabled()) { + LOG.debug("getPublicKey: service=" + domain + "." + service + " key-id=" + keyId); + } + + if (service == null || keyId == null) { + return null; + } + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domain = domain.toLowerCase(); + service = service.toLowerCase(); + keyId = keyId.toLowerCase(); + + // special handling for service sys.auth.zms which is ourselves + // so we'll just lookup our key in our map + + String pubKey = null; + if (isZMSService(domain, service)) { + pubKey = serverPublicKeyMap.get(keyId); + } + + // if it's not the ZMS Server public key then lookup the + // public key from ZMS data + + if (pubKey == null) { + try { + PublicKeyEntry keyEntry = dbService.getServicePublicKeyEntry(domain, service, keyId); + if (keyEntry != null) { + pubKey = keyEntry.getKey(); + } + } catch (ResourceException ex) { + if (LOG.isDebugEnabled()) { + LOG.debug("getPublicKey: unable to get public key: " + ex.getMessage()); + } + return null; + } + } + + if (pubKey == null) { + if (LOG.isWarnEnabled()) { + LOG.warn("getPublicKey: service=" + domain + "." + service + " has no public key registered"); + } + return null; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("getPublicKey: service public key: " + pubKey); + } + + return Crypto.ybase64DecodeString(pubKey); + } + + @Override + public DefaultAdmins putDefaultAdmins(ResourceContext ctx, String domainName, String auditRef, + DefaultAdmins defaultAdmins) { + + final String caller = "putdefaultadmins"; + metric.increment(ZMSConsts.HTTP_PUT); + metric.increment(ZMSConsts.HTTP_REQUEST); + metric.increment(caller); + Object timerMetric = metric.startTiming("putdefaultadmins_timing", null); + + if (LOG.isDebugEnabled()) { + LOG.debug("putDefaultAdmins: domain = " + domainName); + } + + if (readOnlyMode) { + throw ZMSUtils.requestError("Server in Maintenance Read-Only mode. Please try your request later", caller); + } + + try { + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // verify that request is properly authenticated for this request + + verifyAuthorizedServiceOperation(((RsrcCtxWrapper) ctx).principal().getAuthorizedService(), caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + AthenzObject.DEFAULT_ADMINS.convertToLowerCase(defaultAdmins); + + AthenzDomain domain = getAthenzDomain(domainName, false); + if (domain == null) { + throw ZMSUtils.notFoundError("putDefaultAdmins: Domain not found: '" + domainName + "'", caller); + } + + Role adminRole = null; + for (Role role : domain.getRoles()) { + if (ADMIN_ROLE_NAME.equals(ZMSUtils.removeDomainPrefix(role.getName(), domainName, ROLE_PREFIX))) { + adminRole = role; + break; + } + } + if (adminRole == null) { + // if the admin role does not exist in the role section then add it + // this typically should never happen since we have added the + // check to disallow deletion of the admin role but we'll keep + // the logic in place + + if (LOG.isInfoEnabled()) { + LOG.info("putDefaultAdmins: Adding domain admin role because no domain admin role was found for domain: " + domainName); + } + adminRole = ZMSUtils.makeAdminRole(domainName, new ArrayList()); + dbService.executePutRole(ctx, domainName, ADMIN_ROLE_NAME, adminRole, auditRef, caller); + } + + Policy adminPolicy = null; + for (Policy policy : domain.getPolicies()) { + if (ADMIN_POLICY_NAME.equals(ZMSUtils.removeDomainPrefix(policy.getName(), domainName, POLICY_PREFIX))) { + adminPolicy = policy; + break; + } + } + if (adminPolicy == null) { + // if the admin policy does not exist in the policy section then add it + // this typically should never happen since we have added the + // check to disallow deletion of the admin policy but we'll keep + // the logic in place + + if (LOG.isInfoEnabled()) { + LOG.info("putDefaultAdmins: Adding domain admin policy because no domain admin policy was found for domain: " + domainName); + } + //Create and add the admin policy + adminPolicy = ZMSUtils.makeAdminPolicy(domainName, adminRole); + dbService.executePutPolicy(ctx, domainName, ADMIN_POLICY_NAME, adminPolicy, auditRef, caller); + } + + addDefaultAdminAssertion(ctx, domainName, adminPolicy, auditRef, caller); + + removeAdminDenyAssertions(ctx, domainName, domain.getPolicies(), domain.getRoles(), adminRole, + defaultAdmins, auditRef, caller); + + addDefaultAdminMembers(ctx, domainName, adminRole, defaultAdmins, auditRef, caller); + + } catch (Exception exc) { + + String auditDetails = auditListItems("caller specified default-admins", defaultAdmins.getAdmins()); + auditRequestFailure(ctx, exc, domainName, domainName, caller, ZMSConsts.HTTP_PUT, + auditDetails, auditRef); + throw exc; + } + + metric.stopTiming(timerMetric); + return null; + } + + void addDefaultAdminAssertion(ResourceContext ctx, String domainName, Policy adminPolicy, + String auditRef, String caller) { + + if (LOG.isDebugEnabled()) { + LOG.debug("addDefaultAdminAssertion"); + } + + String domainAllResources = domainName + ":*"; + String domainAdminRole = ZMSUtils.roleResourceName(domainName, ADMIN_ROLE_NAME); + + List assertions = adminPolicy.getAssertions(); + if (assertions != null) { + + for (Assertion assertion : assertions) { + String resource = assertion.getResource(); + if (resource == null) { + continue; + } + + String action = assertion.getAction(); + if (action == null) { + continue; + } + + String role = assertion.getRole(); + if (role == null) { + continue; + } + + // default effect is no value is ALLOW + AssertionEffect effect = assertion.getEffect(); + if (effect == null) { + effect = AssertionEffect.ALLOW; + } + + if (resource.equals(domainAllResources) && action.equals("*") && + role.equals(domainAdminRole) && (effect == AssertionEffect.ALLOW)) { + // found an assertion for resource = :*, with action = "*", + // for role = :role.admin and effect = "ALLOW" + // (if effect is null then defaults to ALLOW) so no need to add it + return; + } + } + } + + if (LOG.isInfoEnabled()) { + LOG.info("Adding default admin assertion to admin policy because no default admin assertion was found for admin policy for domain: " + domainName); + } + + ZMSUtils.addAssertion(adminPolicy, domainAllResources, "*", domainAdminRole, AssertionEffect.ALLOW); + dbService.executePutPolicy(ctx, domainName, ADMIN_POLICY_NAME, adminPolicy, auditRef, caller); + } + + void removeAdminDenyAssertions(ResourceContext ctx, String domainName, List policies, + List roles, Role adminRole, DefaultAdmins defaultAdmins, String auditRef, String caller) { + + if (LOG.isDebugEnabled()) { + LOG.debug("removeAdminDenyAssertions"); + } + + for (Policy policy : policies) { + + if (LOG.isDebugEnabled()) { + LOG.debug("access: processing policy: " + policy.getName()); + } + + // Process all the assertions defined in this policy + // As soon as match for an assertion that + // denies access to the admin role is detected, remove it + + List assertions = policy.getAssertions(); + if (assertions == null) { + continue; + } + List assertionsToDelete = new ArrayList<>(); + + for (Assertion assertion : assertions) { + + // If there is no "effect" in the assertion then default is ALLOW + // so continue because logic is looking for DENY + AssertionEffect effect = assertion.getEffect(); + if (effect == null || effect != AssertionEffect.DENY) { + continue; + } + + // If there is no role in the assertion then admin is not being denied + String assertionRole = assertion.getRole(); + if (assertionRole == null) { + continue; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Found DENY assertion for role " + assertionRole); + } + + // role matches admin role then remove it + if (assertionRole.equals(adminRole.getName())) { + assertionsToDelete.add(assertion); + } else { + removeAdminMembers(ctx, domainName, roles, assertionRole, defaultAdmins, auditRef, caller); + } + } + + if (assertionsToDelete.isEmpty()) { + continue; + } + + if (LOG.isInfoEnabled()) { + LOG.info("Removing assertion from policy: " + policy.getName() + " because it was for the domain admin role."); + } + + for (Assertion assertion : assertionsToDelete) { + assertions.remove(assertion); + } + + String policyName = ZMSUtils.removeDomainPrefix(policy.getName(), domainName, POLICY_PREFIX); + if (assertions.size() == 0) { + if (LOG.isInfoEnabled()) { + LOG.info("Removing policy: " + policyName + + " because it did not have any assertions after removing a DENY" + + " assertion for the domain admin role."); + } + + dbService.executeDeletePolicy(ctx, domainName, policyName, auditRef, caller); + } else { + dbService.executePutPolicy(ctx, domainName, policyName, policy, auditRef, caller); + } + } + } + + void removeAdminMembers(ResourceContext ctx, String domainName, List roles, + String assertionRole, DefaultAdmins defaultAdmins, String auditRef, String caller) { + + + for (Role role : roles) { + + if (LOG.isDebugEnabled()) { + LOG.debug("removeAdminMembers: Removing admin members from role: " + role.getName()); + } + + if (!assertionRole.equals(role.getName())) { + continue; + } + + String roleName = ZMSUtils.removeDomainPrefix(role.getName(), domainName, ROLE_PREFIX); + for (String adminName : defaultAdmins.getAdmins()) { + if (isMemberOfRole(role, adminName)) { + if (LOG.isInfoEnabled()) { + LOG.info("removeAdminMembers: removing member: " + adminName + " from role: " + + roleName + " because there is a DENY assertion for this role in this domain."); + } + + dbService.executeDeleteMembership(ctx, domainName, roleName, adminName, auditRef, caller); + } + } + } + } + + void addDefaultAdminMembers(ResourceContext ctx, String domainName, Role adminRole, + DefaultAdmins defaultAdmins, String auditRef, String caller) { + + if (LOG.isDebugEnabled()) { + LOG.debug("addDefaultAdminMembers"); + } + + for (String adminName : defaultAdmins.getAdmins()) { + if (!isMemberOfRole(adminRole, adminName)) { + if (LOG.isInfoEnabled()) { + LOG.info("Adding member: " + adminName + " to admin role for domain: " + domainName); + } + dbService.executePutMembership(ctx, domainName, ADMIN_ROLE_NAME, + adminName, auditRef, caller); + } + } + } + + public ServicePrincipal getServicePrincipal(ResourceContext ctx) { + + final String caller = "getserviceprincipal"; + metric.increment(ZMSConsts.HTTP_GET); + + // we need to make sure we're only validating service/user tokens + // so any thing that's been authenticated by the PrincipalAuthority + // and/or authorities that do not support authorization + + ServicePrincipal servicePrincipal = null; + Principal principal = ((RsrcCtxWrapper) ctx).principal(); + Authority authority = principal.getAuthority(); + + metric.increment(ZMSConsts.HTTP_REQUEST, principal.getDomain()); + metric.increment(caller, principal.getDomain()); + Object timerMetric = metric.startTiming("getserviceprincipal_timing", principal.getDomain()); + + // If the authority is our PrincipalAuthority, then we're not making + // any changes and sending the authenticated token as is. However, + // if the authority does not support authorization then we're going to + // generate a new ServiceToken signed by ZMS and send that back. + + if (authority instanceof com.yahoo.athenz.auth.impl.PrincipalAuthority) { + + servicePrincipal = new ServicePrincipal(); + servicePrincipal.setDomain(principal.getDomain()); + servicePrincipal.setService(principal.getName()); + servicePrincipal.setToken(principal.getCredentials()); + + } else if (!authority.allowAuthorization()) { + + PrincipalToken sdToken = new PrincipalToken(principal.getCredentials()); + PrincipalToken zmsToken = new PrincipalToken.Builder("S1", sdToken.getDomain(), sdToken.getName()) + .issueTime(sdToken.getTimestamp()) + .expirationWindow(sdToken.getExpiryTime() - sdToken.getTimestamp()) + .ip(sdToken.getIP()).keyId(privateKeyId).host(serverHostName) + .keyService(ZMS).build(); + zmsToken.sign(privateKey); + servicePrincipal = new ServicePrincipal(); + servicePrincipal.setDomain(principal.getDomain()); + servicePrincipal.setService(principal.getName()); + servicePrincipal.setToken(zmsToken.getSignedToken()); + + } else { + throw ZMSUtils.requestError("getServicePrincipal: Provided Token is not a Service/User Token", caller); + } + + metric.stopTiming(timerMetric); + return servicePrincipal; + } + + void verifyAuthorizedServiceOperation(String authorizedService, String operationName) { + verifyAuthorizedServiceOperation(authorizedService, operationName, null, null); + } + + /** + * If opItemType and value are not defined in the authorized_services JSON file, + * you can simply pass NULL for these two values. + */ + void verifyAuthorizedServiceOperation(String authorizedService, String operationName, + String opItemType, String opItemVal) { + + // only process this request if we have an authorized service specified + + if (authorizedService == null) { + return; + } + + // lookup the authorized services struct and see if we have the + // service specified in the allowed list + + AuthorizedService authzService = serverAuthorizedServices.get(authorizedService); + if (authzService == null) { + throw ZMSUtils.forbiddenError("Unauthorized Service " + authorizedService, operationName); + } + + // if the list is empty then we allow all the operations + ArrayList ops = authzService.getAllowedOperations(); + if (ops == null || ops.isEmpty()) { + return; + } + + // otherwise make sure the operation is allowed for this service + boolean opAllowed = false; + for (AllowedOperation op : ops) { + if (!op.getName().equalsIgnoreCase(operationName)) { + continue; + } + + opAllowed = op.isOperationAllowedOn(opItemType, opItemVal); + break; + } + + if (!opAllowed) { + throw ZMSUtils.forbiddenError("Unauthorized Operation (" + operationName + + ") for Service " + authorizedService + + (opItemType != null && opItemType != "" ? " on opItemKey " + opItemType + " and opItemVal " + opItemVal : ""), operationName); + } + } + + @Override + public ResourceAccessList getResourceAccessList(ResourceContext ctx, String principal, String action) { + + final String caller = "getresourceaccesslist"; + metric.increment(ZMSConsts.HTTP_GET); + metric.increment(ZMSConsts.HTTP_REQUEST); + metric.increment(caller); + Object timerMetric = metric.startTiming("getresourceaccesslist_timing", null); + + Principal ctxPrincipal = ((RsrcCtxWrapper) ctx).principal(); + if (LOG.isDebugEnabled()) { + LOG.debug("getResourceAccessList:(" + ctxPrincipal + ", " + principal + ", " + action + ")"); + } + + if (principal != null) { + validate(principal, TYPE_ENTITY_NAME, caller); + principal = principal.toLowerCase(); + } + if (action != null) { + validate(action, TYPE_COMPOUND_NAME, caller); + action = action.toLowerCase(); + } + + // if principal is null then we it's a special case + // so we need to make sure the caller is authorized + // to make this request + + if (principal == null || principal.isEmpty()) { + if (!isAllowedResourceLookForAllUsers(ctxPrincipal)) { + throw ZMSUtils.forbiddenError("Principal: " + ctxPrincipal.getYRN() + + " not authorized to lookup resources for all users in Athenz", caller); + } + } + + ResourceAccessList rsrcAccessList = dbService.getResourceAccessList(principal, action); + + metric.stopTiming(timerMetric); + return rsrcAccessList; + } + + static class RsrcCtxWrapper implements ResourceContext { + + com.yahoo.athenz.common.server.rest.ResourceContext ctx = null; + + RsrcCtxWrapper(HttpServletRequest request, + HttpServletResponse response, + Http.AuthorityList authList, Authorizer authorizer) { + ctx = new com.yahoo.athenz.common.server.rest.ResourceContext(request, response, authList, authorizer); + } + + com.yahoo.athenz.common.server.rest.ResourceContext context() { + return ctx; + } + + Principal principal() { + return ctx.principal(); + } + + @Override + public HttpServletRequest request() { + return ctx.request(); + } + + @Override + public HttpServletResponse response() { + return ctx.response(); + } + + @Override + public void authenticate() { + try { + ctx.authenticate(); + } catch (com.yahoo.athenz.common.server.rest.ResourceException restExc) { + throwZmsException(restExc); + } + } + + @Override + public void authorize(String action, String resource, String trustedDomain) { + try { + ctx.authorize(action, resource, trustedDomain); + } catch (com.yahoo.athenz.common.server.rest.ResourceException restExc) { + throwZmsException(restExc); + } + } + + void throwZmsException(com.yahoo.athenz.common.server.rest.ResourceException restExc) { + String msg = null; + Object data = restExc.getData(); + if (data instanceof String) { + msg = (String) data; + } + if (msg == null) { + msg = restExc.getMessage(); + } + throw new com.yahoo.athenz.zms.ResourceException(restExc.getCode(), + new ResourceError().code(restExc.getCode()).message(msg)); + } + } + + public ResourceContext newResourceContext(HttpServletRequest request, HttpServletResponse response) { + return new RsrcCtxWrapper(request, response, authorities, this); + } + + @Override + public Schema getRdlSchema(ResourceContext context) { + return schema; + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSJettyContainer.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSJettyContainer.java new file mode 100644 index 00000000000..36149f4edbc --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSJettyContainer.java @@ -0,0 +1,275 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zms; + +import java.io.File; + +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.Slf4jRequestLog; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.handler.RequestLogHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.rewrite.handler.HeaderPatternRule; +import org.eclipse.jetty.rewrite.handler.RewriteHandler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.glassfish.jersey.servlet.ServletContainer; + +import com.yahoo.athenz.common.server.filters.DefaultMediaTypeFilter; +import com.yahoo.athenz.common.server.log.AthenzRequestLog; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.common.server.rest.Http; +import com.yahoo.athenz.common.server.rest.HttpContainer; + +public class ZMSJettyContainer extends HttpContainer { + + AuditLogger auditLogger = null; + + private static final Logger LOG = LoggerFactory.getLogger(ZMSJettyContainer.class); + + static final String ZMS_DEFAULT_EXCLUDED_CIPHER_SUITES = "SSL_RSA_WITH_DES_CBC_SHA," + + "SSL_DHE_RSA_WITH_DES_CBC_SHA,SSL_DHE_DSS_WITH_DES_CBC_SHA," + + "SSL_RSA_EXPORT_WITH_RC4_40_MD5,SSL_RSA_EXPORT_WITH_DES40_CBC_SHA," + + "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"; + static final String ZMS_DEFAULT_EXCLUDED_PROTOCOLS = "SSLv2,SSLv3"; + + public ZMSJettyContainer(AuditLogger auditLog) { + + auditLogger = auditLog; + authorities = new Http.AuthorityList(); + } + + public void addRequestLogHandler(String rootDir) { + + RequestLogHandler requestLogHandler = new RequestLogHandler(); + + // check to see if have a slf4j logger name specified. if we don't + // then we'll just use our NCSARequestLog extended Athenz logger + // when using the slf4j logger we don't have the option to pass + // our audit logger to keep track of unauthenticated requests + + String accessSlf4jLogger = System.getProperty(ZMSConsts.ZMS_PROP_ACCESS_SLF4J_LOGGER); + if (accessSlf4jLogger != null && !accessSlf4jLogger.isEmpty()) { + + Slf4jRequestLog requestLog = new Slf4jRequestLog(); + requestLog.setLoggerName(accessSlf4jLogger); + requestLog.setExtended(true); + requestLog.setPreferProxiedForAddress(true); + requestLog.setLogTimeZone("GMT"); + requestLogHandler.setRequestLog(requestLog); + + } else { + + String logDir = System.getProperty(ZMSConsts.ZMS_PROP_ACCESS_LOG_DIR, rootDir + "/logs/zms_server"); + String logName = System.getProperty(ZMSConsts.ZMS_PROP_ACCESS_LOG_NAME, "access.yyyy_MM_dd.log"); + + AthenzRequestLog requestLog = new AthenzRequestLog(logDir + File.separator + logName, auditLogger); + requestLog.setAppend(true); + requestLog.setExtended(true); + requestLog.setPreferProxiedForAddress(true); + requestLog.setLogTimeZone("GMT"); + + String retainDays = System.getProperty(ZMSConsts.ZMS_PROP_ACCESS_LOG_RETAIN_DAYS, "31"); + int days = Integer.parseInt(retainDays); + if (days > 0) { + requestLog.setRetainDays(days); + } + requestLogHandler.setRequestLog(requestLog); + } + + getHandlers().addHandler(requestLogHandler); + } + + public void addServletHandlers(String homeDir, String serverHostName) { + + RewriteHandler rewriteHandler = new RewriteHandler(); + + // Check whether or not to disable Keep-Alive support in Jetty. + // This will be the first handler in our array so we always set + // the appropriate header in response. However, since we're now + // behind ATS, we want to keep the connections alive so ATS + // can re-use them as necessary + + boolean keepAlive = Boolean.parseBoolean(System.getProperty(ZMSConsts.ZMS_PROP_KEEP_ALIVE, "true")); + + if (!keepAlive) { + HeaderPatternRule disableKeepAliveRule = new HeaderPatternRule(); + disableKeepAliveRule.setPattern("/*"); + disableKeepAliveRule.setName(HttpHeader.CONNECTION.asString()); + disableKeepAliveRule.setValue(HttpHeaderValue.CLOSE.asString()); + rewriteHandler.addRule(disableKeepAliveRule); + } + + // Return a Host field in the response so during debugging + // we know what server was handling request + + HeaderPatternRule hostNameRule = new HeaderPatternRule(); + hostNameRule.setPattern("/*"); + hostNameRule.setName(HttpHeader.HOST.asString()); + hostNameRule.setValue(serverHostName); + rewriteHandler.addRule(hostNameRule); + + getHandlers().addHandler(rewriteHandler); + + // this sets up the default return media type when client accepts + // any type of media + addContainerRequestFilter(DefaultMediaTypeFilter.class); + + // setup application configuration for authorities, content-providers + // et al + // + com.yahoo.athenz.common.server.rest.RestCoreResourceConfig rconf = + new com.yahoo.athenz.common.server.rest.RestCoreResourceConfig(resources, singletons); + rconf.setAuthorityObject(com.yahoo.athenz.auth.Authorizer.class, authorizer); + rconf.setAuthorityObject(Http.AuthorityList.class, authorities); + rconf.registerAll(); + + // now setup our servlet handler + // + ServletContextHandler servletCtxHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); + servletCtxHandler.setContextPath("/"); + + ServletHolder holder = new ServletHolder(new ServletContainer(rconf)); + servletCtxHandler.addServlet(holder, "/*"); + + getHandlers().addHandler(servletCtxHandler); + } + + public HttpConfiguration newHttpConfiguration(int httpsPort) { + + // HTTP Configuration + + boolean sendServerVersion = Boolean.parseBoolean(System.getProperty(ZMSConsts.ZMS_PROP_SEND_SERVER_VERSION, "false")); + boolean sendDateHeader = Boolean.parseBoolean(System.getProperty(ZMSConsts.ZMS_PROP_SEND_DATE_HEADER, "false")); + int outputBufferSize = Integer.parseInt(System.getProperty(ZMSConsts.ZMS_PROP_OUTPUT_BUFFER_SIZE, "32768")); + int requestHeaderSize = Integer.parseInt(System.getProperty(ZMSConsts.ZMS_PROP_REQUEST_HEADER_SIZE, "8192")); + int responseHeaderSize = Integer.parseInt(System.getProperty(ZMSConsts.ZMS_PROP_RESPONSE_HEADER_SIZE, "8192")); + + HttpConfiguration httpConfig = new HttpConfiguration(); + + if (httpsPort > 0) { + httpConfig.setSecureScheme("https"); + httpConfig.setSecurePort(httpsPort); + } + + httpConfig.setOutputBufferSize(outputBufferSize); + httpConfig.setRequestHeaderSize(requestHeaderSize); + httpConfig.setResponseHeaderSize(responseHeaderSize); + httpConfig.setSendServerVersion(sendServerVersion); + httpConfig.setSendDateHeader(sendDateHeader); + + return httpConfig; + } + + SslContextFactory createSSLContextObject() { + + String keyStorePath = System.getProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PATH); + String keyStorePassword = System.getProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PASSWORD); + String keyStoreType = System.getProperty(ZMSConsts.ZMS_PROP_KEYSTORE_TYPE, "PKCS12"); + String keyManagerPassword = System.getProperty(ZMSConsts.ZMS_PROP_KEYMANAGER_PASSWORD); + String trustStorePath = System.getProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PATH); + String trustStorePassword = System.getProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PASSWORD); + String trustStoreType = System.getProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + String excludedCipherSuites = System.getProperty(ZMSConsts.ZMS_PROP_EXCLUDED_CIPHER_SUITES, + ZMS_DEFAULT_EXCLUDED_CIPHER_SUITES); + String excludedProtocols = System.getProperty(ZMSConsts.ZMS_PROP_EXCLUDED_PROTOCOLS, + ZMS_DEFAULT_EXCLUDED_PROTOCOLS); + + SslContextFactory sslContextFactory = new SslContextFactory(); + if (keyStorePath != null) { + LOG.info("Using SSL KeyStore path: " + keyStorePath); + sslContextFactory.setKeyStorePath(keyStorePath); + } + if (keyStorePassword != null) { + sslContextFactory.setKeyStorePassword(keyStorePassword); + } + sslContextFactory.setKeyStoreType(keyStoreType); + + if (keyManagerPassword != null) { + sslContextFactory.setKeyManagerPassword(keyManagerPassword); + } + if (trustStorePath != null) { + LOG.info("Using SSL TrustStore path: " + trustStorePath); + sslContextFactory.setTrustStorePath(trustStorePath); + } + if (trustStorePassword != null) { + sslContextFactory.setTrustStorePassword(trustStorePassword); + } + sslContextFactory.setTrustStoreType(trustStoreType); + + if (excludedCipherSuites.length() != 0) { + sslContextFactory.setExcludeCipherSuites(excludedCipherSuites.split(",")); + } + + if (excludedProtocols.length() != 0) { + sslContextFactory.setExcludeProtocols(excludedProtocols.split(",")); + } + + return sslContextFactory; + } + + public void addHTTPConnectors(HttpConfiguration httpConfig, int httpPort, int httpsPort) { + + int idleTimeout = Integer.parseInt(System.getProperty(ZMSConsts.ZMS_PROP_IDLE_TIMEOUT, "30000")); + String listenHost = System.getProperty(ZMSConsts.ZMS_PROP_LISTEN_HOST); + + // HTTP Connector + + if (httpPort > 0) { + ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); + if (listenHost != null) { + connector.setHost(listenHost); + } + connector.setPort(httpPort); + connector.setIdleTimeout(idleTimeout); + server.addConnector(connector); + } + + // HTTPS Connector + + if (httpsPort > 0) { + + // SSL Context Factory + + SslContextFactory sslContextFactory = createSSLContextObject(); + + // SSL HTTP Configuration + + HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); + httpsConfig.addCustomizer(new SecureRequestCustomizer()); + + // SSL Connector + + ServerConnector sslConnector = new ServerConnector(server, + new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), + new HttpConnectionFactory(httpsConfig)); + sslConnector.setPort(httpsPort); + sslConnector.setIdleTimeout(idleTimeout); + server.addConnector(sslConnector); + } + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSResources.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSResources.java new file mode 100644 index 00000000000..969be24340d --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSResources.java @@ -0,0 +1,1843 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zms; + +import com.yahoo.rdl.*; +import java.util.*; +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.inject.Inject; + +@Path("/zms/v1") +public class ZMSResources { + + @GET + @Path("/domain/{domain}") + @Produces(MediaType.APPLICATION_JSON) + public Domain getDomain(@PathParam("domain") String domain) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Domain e = this.delegate.getDomain(context, domain); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getDomain"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain") + @Produces(MediaType.APPLICATION_JSON) + public DomainList getDomainList(@QueryParam("limit") Integer limit, @QueryParam("skip") String skip, @QueryParam("prefix") String prefix, @QueryParam("depth") Integer depth, @QueryParam("account") String account, @QueryParam("ypmid") Integer productId, @QueryParam("member") String roleMember, @QueryParam("role") String roleName, @HeaderParam("If-Modified-Since") String modifiedSince) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + DomainList e = this.delegate.getDomainList(context, limit, skip, prefix, depth, account, productId, roleMember, roleName, modifiedSince); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getDomainList"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @POST + @Path("/domain") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Domain postTopLevelDomain(@HeaderParam("Y-Audit-Ref") String auditRef, TopLevelDomain detail) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("create", "sys.auth:domain", null); + Domain e = this.delegate.postTopLevelDomain(context, auditRef, detail); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource postTopLevelDomain"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @POST + @Path("/subdomain/{parent}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Domain postSubDomain(@PathParam("parent") String parent, @HeaderParam("Y-Audit-Ref") String auditRef, SubDomain detail) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("create", "" + parent + ":domain", null); + Domain e = this.delegate.postSubDomain(context, parent, auditRef, detail); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource postSubDomain"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @POST + @Path("/userdomain/{name}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Domain postUserDomain(@PathParam("name") String name, @HeaderParam("Y-Audit-Ref") String auditRef, UserDomain detail) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("create", "user." + name + ":domain", null); + Domain e = this.delegate.postUserDomain(context, name, auditRef, detail); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource postUserDomain"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{name}") + @Produces(MediaType.APPLICATION_JSON) + public TopLevelDomain deleteTopLevelDomain(@PathParam("name") String name, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("delete", "sys.auth:domain", null); + TopLevelDomain e = this.delegate.deleteTopLevelDomain(context, name, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteTopLevelDomain"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/subdomain/{parent}/{name}") + @Produces(MediaType.APPLICATION_JSON) + public SubDomain deleteSubDomain(@PathParam("parent") String parent, @PathParam("name") String name, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("delete", "" + parent + ":domain", null); + SubDomain e = this.delegate.deleteSubDomain(context, parent, name, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteSubDomain"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/userdomain/{name}") + @Produces(MediaType.APPLICATION_JSON) + public UserDomain deleteUserDomain(@PathParam("name") String name, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("delete", "user." + name + ":domain", null); + UserDomain e = this.delegate.deleteUserDomain(context, name, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteUserDomain"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{name}/meta") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Domain putDomainMeta(@PathParam("name") String name, @HeaderParam("Y-Audit-Ref") String auditRef, DomainMeta detail) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + name + ":", null); + Domain e = this.delegate.putDomainMeta(context, name, auditRef, detail); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putDomainMeta"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{name}/template") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public DomainTemplate putDomainTemplate(@PathParam("name") String name, @HeaderParam("Y-Audit-Ref") String auditRef, DomainTemplate template) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + name + ":", null); + DomainTemplate e = this.delegate.putDomainTemplate(context, name, auditRef, template); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putDomainTemplate"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{name}/template") + @Produces(MediaType.APPLICATION_JSON) + public DomainTemplateList getDomainTemplateList(@PathParam("name") String name) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + DomainTemplateList e = this.delegate.getDomainTemplateList(context, name); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getDomainTemplateList"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{name}/template/{template}") + @Produces(MediaType.APPLICATION_JSON) + public DomainTemplate deleteDomainTemplate(@PathParam("name") String name, @PathParam("template") String template, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("delete", "" + name + ":", null); + DomainTemplate e = this.delegate.deleteDomainTemplate(context, name, template, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteDomainTemplate"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/check") + @Produces(MediaType.APPLICATION_JSON) + public DomainDataCheck getDomainDataCheck(@PathParam("domainName") String domainName) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + DomainDataCheck e = this.delegate.getDomainDataCheck(context, domainName); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getDomainDataCheck"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domainName}/entity/{entityName}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Entity putEntity(@PathParam("domainName") String domainName, @PathParam("entityName") String entityName, @HeaderParam("Y-Audit-Ref") String auditRef, Entity entity) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domainName + ":" + entityName + "", null); + Entity e = this.delegate.putEntity(context, domainName, entityName, auditRef, entity); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putEntity"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/entity/{entityName}") + @Produces(MediaType.APPLICATION_JSON) + public Entity getEntity(@PathParam("domainName") String domainName, @PathParam("entityName") String entityName) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Entity e = this.delegate.getEntity(context, domainName, entityName); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getEntity"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{domainName}/entity/{entityName}") + @Produces(MediaType.APPLICATION_JSON) + public Entity deleteEntity(@PathParam("domainName") String domainName, @PathParam("entityName") String entityName, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("delete", "" + domainName + ":" + entityName + "", null); + Entity e = this.delegate.deleteEntity(context, domainName, entityName, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteEntity"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/entity") + @Produces(MediaType.APPLICATION_JSON) + public EntityList getEntityList(@PathParam("domainName") String domainName) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + EntityList e = this.delegate.getEntityList(context, domainName); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getEntityList"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/role") + @Produces(MediaType.APPLICATION_JSON) + public RoleList getRoleList(@PathParam("domainName") String domainName, @QueryParam("limit") Integer limit, @QueryParam("skip") String skip) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + RoleList e = this.delegate.getRoleList(context, domainName, limit, skip); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getRoleList"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/roles") + @Produces(MediaType.APPLICATION_JSON) + public Roles getRoles(@PathParam("domainName") String domainName, @QueryParam("members") @DefaultValue("false") Boolean members) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Roles e = this.delegate.getRoles(context, domainName, members); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getRoles"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/role/{roleName}") + @Produces(MediaType.APPLICATION_JSON) + public Role getRole(@PathParam("domainName") String domainName, @PathParam("roleName") String roleName, @QueryParam("auditLog") @DefaultValue("false") Boolean auditLog, @QueryParam("expand") @DefaultValue("false") Boolean expand) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Role e = this.delegate.getRole(context, domainName, roleName, auditLog, expand); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getRole"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domainName}/role/{roleName}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Role putRole(@PathParam("domainName") String domainName, @PathParam("roleName") String roleName, @HeaderParam("Y-Audit-Ref") String auditRef, Role role) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domainName + ":role." + roleName + "", null); + Role e = this.delegate.putRole(context, domainName, roleName, auditRef, role); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putRole"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{domainName}/role/{roleName}") + @Produces(MediaType.APPLICATION_JSON) + public Role deleteRole(@PathParam("domainName") String domainName, @PathParam("roleName") String roleName, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("delete", "" + domainName + ":role." + roleName + "", null); + Role e = this.delegate.deleteRole(context, domainName, roleName, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteRole"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/role/{roleName}/member/{memberName}") + @Produces(MediaType.APPLICATION_JSON) + public Membership getMembership(@PathParam("domainName") String domainName, @PathParam("roleName") String roleName, @PathParam("memberName") String memberName) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Membership e = this.delegate.getMembership(context, domainName, roleName, memberName); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getMembership"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domainName}/role/{roleName}/member/{memberName}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Membership putMembership(@PathParam("domainName") String domainName, @PathParam("roleName") String roleName, @PathParam("memberName") String memberName, @HeaderParam("Y-Audit-Ref") String auditRef, Membership membership) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domainName + ":role." + roleName + "", null); + Membership e = this.delegate.putMembership(context, domainName, roleName, memberName, auditRef, membership); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putMembership"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{domainName}/role/{roleName}/member/{memberName}") + @Produces(MediaType.APPLICATION_JSON) + public Membership deleteMembership(@PathParam("domainName") String domainName, @PathParam("roleName") String roleName, @PathParam("memberName") String memberName, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domainName + ":role." + roleName + "", null); + Membership e = this.delegate.deleteMembership(context, domainName, roleName, memberName, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteMembership"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domainName}/admins") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public DefaultAdmins putDefaultAdmins(@PathParam("domainName") String domainName, @HeaderParam("Y-Audit-Ref") String auditRef, DefaultAdmins defaultAdmins) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "sys.auth:domain", null); + DefaultAdmins e = this.delegate.putDefaultAdmins(context, domainName, auditRef, defaultAdmins); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putDefaultAdmins"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/policy") + @Produces(MediaType.APPLICATION_JSON) + public PolicyList getPolicyList(@PathParam("domainName") String domainName, @QueryParam("limit") Integer limit, @QueryParam("skip") String skip) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + PolicyList e = this.delegate.getPolicyList(context, domainName, limit, skip); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getPolicyList"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/policies") + @Produces(MediaType.APPLICATION_JSON) + public Policies getPolicies(@PathParam("domainName") String domainName, @QueryParam("assertions") @DefaultValue("false") Boolean assertions) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Policies e = this.delegate.getPolicies(context, domainName, assertions); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getPolicies"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/policy/{policyName}") + @Produces(MediaType.APPLICATION_JSON) + public Policy getPolicy(@PathParam("domainName") String domainName, @PathParam("policyName") String policyName) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Policy e = this.delegate.getPolicy(context, domainName, policyName); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getPolicy"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domainName}/policy/{policyName}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Policy putPolicy(@PathParam("domainName") String domainName, @PathParam("policyName") String policyName, @HeaderParam("Y-Audit-Ref") String auditRef, Policy policy) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domainName + ":policy." + policyName + "", null); + Policy e = this.delegate.putPolicy(context, domainName, policyName, auditRef, policy); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putPolicy"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{domainName}/policy/{policyName}") + @Produces(MediaType.APPLICATION_JSON) + public Policy deletePolicy(@PathParam("domainName") String domainName, @PathParam("policyName") String policyName, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("delete", "" + domainName + ":policy." + policyName + "", null); + Policy e = this.delegate.deletePolicy(context, domainName, policyName, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deletePolicy"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/policy/{policyName}/assertion/{assertionId}") + @Produces(MediaType.APPLICATION_JSON) + public Assertion getAssertion(@PathParam("domainName") String domainName, @PathParam("policyName") String policyName, @PathParam("assertionId") Long assertionId) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Assertion e = this.delegate.getAssertion(context, domainName, policyName, assertionId); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getAssertion"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domainName}/policy/{policyName}/assertion") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Assertion putAssertion(@PathParam("domainName") String domainName, @PathParam("policyName") String policyName, @HeaderParam("Y-Audit-Ref") String auditRef, Assertion assertion) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domainName + ":policy." + policyName + "", null); + Assertion e = this.delegate.putAssertion(context, domainName, policyName, auditRef, assertion); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.CREATED: + throw typedException(code, e, Assertion.class); + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putAssertion"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{domainName}/policy/{policyName}/assertion/{assertionId}") + @Produces(MediaType.APPLICATION_JSON) + public Assertion deleteAssertion(@PathParam("domainName") String domainName, @PathParam("policyName") String policyName, @PathParam("assertionId") Long assertionId, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domainName + ":policy." + policyName + "", null); + Assertion e = this.delegate.deleteAssertion(context, domainName, policyName, assertionId, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteAssertion"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domain}/service/{service}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public ServiceIdentity putServiceIdentity(@PathParam("domain") String domain, @PathParam("service") String service, @HeaderParam("Y-Audit-Ref") String auditRef, ServiceIdentity detail) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domain + ":service", null); + ServiceIdentity e = this.delegate.putServiceIdentity(context, domain, service, auditRef, detail); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putServiceIdentity"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domain}/service/{service}") + @Produces(MediaType.APPLICATION_JSON) + public ServiceIdentity getServiceIdentity(@PathParam("domain") String domain, @PathParam("service") String service) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + ServiceIdentity e = this.delegate.getServiceIdentity(context, domain, service); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getServiceIdentity"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{domain}/service/{service}") + @Produces(MediaType.APPLICATION_JSON) + public ServiceIdentity deleteServiceIdentity(@PathParam("domain") String domain, @PathParam("service") String service, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("delete", "" + domain + ":service", null); + ServiceIdentity e = this.delegate.deleteServiceIdentity(context, domain, service, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteServiceIdentity"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/services") + @Produces(MediaType.APPLICATION_JSON) + public ServiceIdentities getServiceIdentities(@PathParam("domainName") String domainName, @QueryParam("publickeys") @DefaultValue("false") Boolean publickeys, @QueryParam("hosts") @DefaultValue("false") Boolean hosts) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + ServiceIdentities e = this.delegate.getServiceIdentities(context, domainName, publickeys, hosts); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getServiceIdentities"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/service") + @Produces(MediaType.APPLICATION_JSON) + public ServiceIdentityList getServiceIdentityList(@PathParam("domainName") String domainName, @QueryParam("limit") Integer limit, @QueryParam("skip") String skip) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + ServiceIdentityList e = this.delegate.getServiceIdentityList(context, domainName, limit, skip); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getServiceIdentityList"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domain}/service/{service}/publickey/{id}") + @Produces(MediaType.APPLICATION_JSON) + public PublicKeyEntry getPublicKeyEntry(@PathParam("domain") String domain, @PathParam("service") String service, @PathParam("id") String id) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + PublicKeyEntry e = this.delegate.getPublicKeyEntry(context, domain, service, id); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getPublicKeyEntry"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domain}/service/{service}/publickey/{id}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public PublicKeyEntry putPublicKeyEntry(@PathParam("domain") String domain, @PathParam("service") String service, @PathParam("id") String id, @HeaderParam("Y-Audit-Ref") String auditRef, PublicKeyEntry publicKeyEntry) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domain + ":service." + service + "", null); + PublicKeyEntry e = this.delegate.putPublicKeyEntry(context, domain, service, id, auditRef, publicKeyEntry); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putPublicKeyEntry"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{domain}/service/{service}/publickey/{id}") + @Produces(MediaType.APPLICATION_JSON) + public PublicKeyEntry deletePublicKeyEntry(@PathParam("domain") String domain, @PathParam("service") String service, @PathParam("id") String id, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domain + ":service." + service + "", null); + PublicKeyEntry e = this.delegate.deletePublicKeyEntry(context, domain, service, id, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deletePublicKeyEntry"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domain}/tenancy/{service}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Tenancy putTenancy(@PathParam("domain") String domain, @PathParam("service") String service, @HeaderParam("Y-Audit-Ref") String auditRef, Tenancy detail) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domain + ":tenancy", null); + Tenancy e = this.delegate.putTenancy(context, domain, service, auditRef, detail); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putTenancy"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domain}/tenancy/{service}") + @Produces(MediaType.APPLICATION_JSON) + public Tenancy getTenancy(@PathParam("domain") String domain, @PathParam("service") String service) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Tenancy e = this.delegate.getTenancy(context, domain, service); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getTenancy"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{domain}/tenancy/{service}") + @Produces(MediaType.APPLICATION_JSON) + public Tenancy deleteTenancy(@PathParam("domain") String domain, @PathParam("service") String service, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("delete", "" + domain + ":tenancy", null); + Tenancy e = this.delegate.deleteTenancy(context, domain, service, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteTenancy"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domain}/tenancy/{service}/resourceGroup/{resourceGroup}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public TenancyResourceGroup putTenancyResourceGroup(@PathParam("domain") String domain, @PathParam("service") String service, @PathParam("resourceGroup") String resourceGroup, @HeaderParam("Y-Audit-Ref") String auditRef, TenancyResourceGroup detail) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domain + ":tenancy." + service + "", null); + TenancyResourceGroup e = this.delegate.putTenancyResourceGroup(context, domain, service, resourceGroup, auditRef, detail); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putTenancyResourceGroup"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{domain}/tenancy/{service}/resourceGroup/{resourceGroup}") + @Produces(MediaType.APPLICATION_JSON) + public TenancyResourceGroup deleteTenancyResourceGroup(@PathParam("domain") String domain, @PathParam("service") String service, @PathParam("resourceGroup") String resourceGroup, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domain + ":tenancy." + service + "", null); + TenancyResourceGroup e = this.delegate.deleteTenancyResourceGroup(context, domain, service, resourceGroup, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteTenancyResourceGroup"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domain}/service/{service}/tenant/{tenantDomain}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public TenantRoles putTenantRoles(@PathParam("domain") String domain, @PathParam("service") String service, @PathParam("tenantDomain") String tenantDomain, @HeaderParam("Y-Audit-Ref") String auditRef, TenantRoles detail) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domain + ":tenant." + tenantDomain + "", null); + TenantRoles e = this.delegate.putTenantRoles(context, domain, service, tenantDomain, auditRef, detail); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.CREATED: + throw typedException(code, e, TenantRoles.class); + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putTenantRoles"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domain}/service/{service}/tenant/{tenantDomain}") + @Produces(MediaType.APPLICATION_JSON) + public TenantRoles getTenantRoles(@PathParam("domain") String domain, @PathParam("service") String service, @PathParam("tenantDomain") String tenantDomain) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + TenantRoles e = this.delegate.getTenantRoles(context, domain, service, tenantDomain); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getTenantRoles"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{domain}/service/{service}/tenant/{tenantDomain}") + @Produces(MediaType.APPLICATION_JSON) + public TenantRoles deleteTenantRoles(@PathParam("domain") String domain, @PathParam("service") String service, @PathParam("tenantDomain") String tenantDomain, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("delete", "" + domain + ":tenant." + tenantDomain + "", null); + TenantRoles e = this.delegate.deleteTenantRoles(context, domain, service, tenantDomain, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteTenantRoles"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public TenantResourceGroupRoles putTenantResourceGroupRoles(@PathParam("domain") String domain, @PathParam("service") String service, @PathParam("tenantDomain") String tenantDomain, @PathParam("resourceGroup") String resourceGroup, @HeaderParam("Y-Audit-Ref") String auditRef, TenantResourceGroupRoles detail) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domain + ":tenant." + tenantDomain + "", null); + TenantResourceGroupRoles e = this.delegate.putTenantResourceGroupRoles(context, domain, service, tenantDomain, resourceGroup, auditRef, detail); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.CREATED: + throw typedException(code, e, TenantResourceGroupRoles.class); + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putTenantResourceGroupRoles"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + @Produces(MediaType.APPLICATION_JSON) + public TenantResourceGroupRoles getTenantResourceGroupRoles(@PathParam("domain") String domain, @PathParam("service") String service, @PathParam("tenantDomain") String tenantDomain, @PathParam("resourceGroup") String resourceGroup) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + TenantResourceGroupRoles e = this.delegate.getTenantResourceGroupRoles(context, domain, service, tenantDomain, resourceGroup); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getTenantResourceGroupRoles"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{domain}/service/{service}/tenant/{tenantDomain}/resourceGroup/{resourceGroup}") + @Produces(MediaType.APPLICATION_JSON) + public TenantResourceGroupRoles deleteTenantResourceGroupRoles(@PathParam("domain") String domain, @PathParam("service") String service, @PathParam("tenantDomain") String tenantDomain, @PathParam("resourceGroup") String resourceGroup, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + domain + ":tenant." + tenantDomain + "", null); + TenantResourceGroupRoles e = this.delegate.deleteTenantResourceGroupRoles(context, domain, service, tenantDomain, resourceGroup, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteTenantResourceGroupRoles"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @PUT + @Path("/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public ProviderResourceGroupRoles putProviderResourceGroupRoles(@PathParam("tenantDomain") String tenantDomain, @PathParam("provDomain") String provDomain, @PathParam("provService") String provService, @PathParam("resourceGroup") String resourceGroup, @HeaderParam("Y-Audit-Ref") String auditRef, ProviderResourceGroupRoles detail) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + tenantDomain + ":tenancy." + provDomain + "." + provService + "", null); + ProviderResourceGroupRoles e = this.delegate.putProviderResourceGroupRoles(context, tenantDomain, provDomain, provService, resourceGroup, auditRef, detail); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.CREATED: + throw typedException(code, e, ProviderResourceGroupRoles.class); + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource putProviderResourceGroupRoles"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + @Produces(MediaType.APPLICATION_JSON) + public ProviderResourceGroupRoles getProviderResourceGroupRoles(@PathParam("tenantDomain") String tenantDomain, @PathParam("provDomain") String provDomain, @PathParam("provService") String provService, @PathParam("resourceGroup") String resourceGroup) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + ProviderResourceGroupRoles e = this.delegate.getProviderResourceGroupRoles(context, tenantDomain, provDomain, provService, resourceGroup); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getProviderResourceGroupRoles"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @DELETE + @Path("/domain/{tenantDomain}/provDomain/{provDomain}/provService/{provService}/resourceGroup/{resourceGroup}") + @Produces(MediaType.APPLICATION_JSON) + public ProviderResourceGroupRoles deleteProviderResourceGroupRoles(@PathParam("tenantDomain") String tenantDomain, @PathParam("provDomain") String provDomain, @PathParam("provService") String provService, @PathParam("resourceGroup") String resourceGroup, @HeaderParam("Y-Audit-Ref") String auditRef) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authorize("update", "" + tenantDomain + ":tenancy." + provDomain + "." + provService + "", null); + ProviderResourceGroupRoles e = this.delegate.deleteProviderResourceGroupRoles(context, tenantDomain, provDomain, provService, resourceGroup, auditRef); + return null; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.CONFLICT: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource deleteProviderResourceGroupRoles"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/access/{action}/{resource}") + @Produces(MediaType.APPLICATION_JSON) + public Access getAccess(@PathParam("action") String action, @PathParam("resource") String resource, @QueryParam("domain") String domain, @QueryParam("principal") String checkPrincipal) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Access e = this.delegate.getAccess(context, action, resource, domain, checkPrincipal); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getAccess"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/access/{action}") + @Produces(MediaType.APPLICATION_JSON) + public Access getAccessExt(@PathParam("action") String action, @QueryParam("resource") String resource, @QueryParam("domain") String domain, @QueryParam("principal") String checkPrincipal) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Access e = this.delegate.getAccessExt(context, action, resource, domain, checkPrincipal); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getAccessExt"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/resource") + @Produces(MediaType.APPLICATION_JSON) + public ResourceAccessList getResourceAccessList(@QueryParam("principal") String principal, @QueryParam("action") String action) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + ResourceAccessList e = this.delegate.getResourceAccessList(context, principal, action); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getResourceAccessList"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/sys/modified_domains") + @Produces(MediaType.APPLICATION_JSON) + public void getSignedDomains(@QueryParam("domain") String domain, @QueryParam("metaonly") String metaOnly, @HeaderParam("If-None-Match") String matchingTag) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + GetSignedDomainsResult result = new GetSignedDomainsResult(context); + this.delegate.getSignedDomains(context, domain, metaOnly, matchingTag, result); + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.NOT_MODIFIED: + throw typedException(code, e, void.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getSignedDomains"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/user/{userName}/token") + @Produces(MediaType.APPLICATION_JSON) + public UserToken getUserToken(@PathParam("userName") String userName, @QueryParam("services") String serviceNames) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + UserToken e = this.delegate.getUserToken(context, userName, serviceNames); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getUserToken"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @OPTIONS + @Path("/user/{userName}/token") + @Produces(MediaType.APPLICATION_JSON) + public UserToken optionsUserToken(@PathParam("userName") String userName, @QueryParam("services") String serviceNames) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + UserToken e = this.delegate.optionsUserToken(context, userName, serviceNames); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource optionsUserToken"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/principal") + @Produces(MediaType.APPLICATION_JSON) + public ServicePrincipal getServicePrincipal() { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + ServicePrincipal e = this.delegate.getServicePrincipal(context); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getServicePrincipal"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/template") + @Produces(MediaType.APPLICATION_JSON) + public ServerTemplateList getServerTemplateList() { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + ServerTemplateList e = this.delegate.getServerTemplateList(context); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getServerTemplateList"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/template/{template}") + @Produces(MediaType.APPLICATION_JSON) + public Template getTemplate(@PathParam("template") String template) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Template e = this.delegate.getTemplate(context, template); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getTemplate"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/schema") + @Produces(MediaType.APPLICATION_JSON) + public Schema getRdlSchema() { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + Schema e = this.delegate.getRdlSchema(context); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getRdlSchema"); + throw typedException(code, e, ResourceError.class); + } + } + } + + + WebApplicationException typedException(int code, ResourceException e, Class eClass) { + Object data = e.getData(); + Object entity = eClass.isInstance(data) ? data : null; + if (entity != null) { + return new WebApplicationException(Response.status(code).entity(entity).build()); + } else { + return new WebApplicationException(code); + } + } + + @Inject private ZMSHandler delegate; + @Context private HttpServletRequest request; + @Context private HttpServletResponse response; + +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSServerImpl.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSServerImpl.java new file mode 100644 index 00000000000..b52812206a2 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSServerImpl.java @@ -0,0 +1,109 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.AuthorityKeyStore; +import com.yahoo.athenz.auth.Authorizer; +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.metrics.MetricFactory; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.common.server.rest.Http.AuthorityList; +import com.yahoo.athenz.zms.pkey.PrivateKeyStore; +import com.yahoo.athenz.zms.pkey.PrivateKeyStoreFactory; +import com.yahoo.athenz.zms.store.ObjectStore; +import com.yahoo.athenz.zms.store.file.FileObjectStore; +import com.yahoo.athenz.zms.store.jdbc.DataSourceFactory; +import com.yahoo.athenz.zms.store.jdbc.JDBCObjectStore; +import com.yahoo.athenz.zms.store.jdbc.PoolableDataSource; + +import java.io.File; +import java.security.PrivateKey; + +public class ZMSServerImpl { + + ZMSImpl instance = null; + + AuditLogger auditLogger = null; + String auditLoggerMsgBldrClass = null; + + public ZMSServerImpl(String serverHostName, String db_context, + PrivateKeyStoreFactory pkeyStoreFactory, MetricFactory metricFactory, + AuditLogger auditLog, String auditLogMsgBldrClass, AuthorityList authList) { + + auditLogger = auditLog; + auditLoggerMsgBldrClass = auditLogMsgBldrClass; + + // extract the private key and public keys for our service + + StringBuilder privKeyId = new StringBuilder(256); + PrivateKeyStore keyStore = pkeyStoreFactory.create(serverHostName); + PrivateKey pkey = keyStore.getPrivateKey(privKeyId); + String publicKey = keyStore.getPEMPublicKey(); + + // create our metric and increment our startup count + + Metric metric = metricFactory.create(); + metric.increment("zms_sa_startup"); + + ObjectStore store = null; + if (db_context != null && db_context.startsWith("jdbc:")) { + PoolableDataSource src = DataSourceFactory.create(db_context); + store = new JDBCObjectStore(src); + } else { + String fileDirName = System.getProperty(ZMSConsts.ZMS_PROP_DB_TABLE, "zms_root"); + String path = getFileStructPath(db_context, fileDirName); + store = new FileObjectStore(new File(path)); + } + + try { + instance = new ZMSImpl(serverHostName, store, metric, pkey, privKeyId.toString(), + publicKey, auditLogger, auditLoggerMsgBldrClass); + instance.putAuthorityList(authList); + } catch (Exception ex) { + metric.increment("zms_startup_fail_sum"); + throw ex; + } + + // make sure to set the keystore for any instance that requires it + + for (Authority authority : authList.getAuthorities()) { + if (AuthorityKeyStore.class.isInstance(authority)) { + ((AuthorityKeyStore) authority).setKeyStore(instance); + } + } + } + + String getFileStructPath(String db_context, String name) { + + String path = db_context; + if (path == null) { + path = name; + } else if (name != null) { + path = path + File.separator + name; + } + + return path; + } + + public Authorizer getAuthorizer() { + return instance; + } + + public ZMSHandler getInstance() { + return instance; + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/config/AllowedOperation.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/config/AllowedOperation.java new file mode 100644 index 00000000000..63ba549a171 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/config/AllowedOperation.java @@ -0,0 +1,94 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.config; + +import java.util.Set; +import java.util.Map; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class AllowedOperation { + private String name; + private Map> items; + + public Map> getItems() { + return items; + } + + public void setItems(Map> items) { + this.items = items; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isOperationAllowedOn(String opItemType, String opItemValue) { + + // if no operationItems are defined, always allow all + if (this.items == null || this.items.isEmpty()) { + return true; + } + + // if there are operations defined, and opItemType or opItemValue are empty, return false + if (opItemType == null || opItemValue == null) { + return false; + } + + // if not empty, check and make sure the opItem type + value is found. + opItemType = opItemType.toLowerCase(); + opItemValue = opItemValue.toLowerCase(); + Set opItems = this.items.get(opItemType); + return opItems != null && opItems.contains(opItemValue); + } + + @Override + public int hashCode() { + return items.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (getClass() != obj.getClass()) { + return false; + } + + AllowedOperation other = (AllowedOperation) obj; + + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + + return true; + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/config/AuthorizedService.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/config/AuthorizedService.java new file mode 100644 index 00000000000..1524dacb09c --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/config/AuthorizedService.java @@ -0,0 +1,34 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.config; + +import java.util.ArrayList; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class AuthorizedService { + + private ArrayList allowedOperations; + + public ArrayList getAllowedOperations() { + return allowedOperations; + } + + public void setAllowedOperations(ArrayList allowedOperations) { + this.allowedOperations = allowedOperations; + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/config/AuthorizedServices.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/config/AuthorizedServices.java new file mode 100644 index 00000000000..5cac2337021 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/config/AuthorizedServices.java @@ -0,0 +1,46 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.config; + +import java.util.HashMap; +import java.util.Set; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class AuthorizedServices { + private HashMap services; + + public HashMap getServices() { + return services; + } + + public void setTemplates(HashMap services) { + this.services = services; + } + + public AuthorizedService get(String name) { + return services.get(name); + } + + public boolean contains(String name) { + return services.containsKey(name); + } + + public Set names() { + return services.keySet(); + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/config/SolutionTemplates.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/config/SolutionTemplates.java new file mode 100644 index 00000000000..a1d9435ab06 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/config/SolutionTemplates.java @@ -0,0 +1,47 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.config; + +import java.util.HashMap; +import java.util.Set; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.yahoo.athenz.zms.Template; + +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class SolutionTemplates { + private HashMap templates; + + public HashMap getTemplates() { + return templates; + } + + public void setTemplates(HashMap templates) { + this.templates = templates; + } + + public Template get(String name) { + return templates.get(name); + } + + public boolean contains(String name) { + return templates.containsKey(name); + } + + public Set names() { + return templates.keySet(); + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/PrivateKeyStore.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/PrivateKeyStore.java new file mode 100644 index 00000000000..0845c8d4dfc --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/PrivateKeyStore.java @@ -0,0 +1,41 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.pkey; + +import java.security.PrivateKey; + +public interface PrivateKeyStore { + + /** + * Retrieve private key for this ZMS Server instance to sign its tokens + * The private key identifier must be updated in the privateKeyId out + * StringBuilder field. The Private Key Store Factory has the knowledge + * which hostname we're processing this request for. + * @param privateKeyId - out argument - must be updated to include key id + * @return private key for this ZMS Server instance. + */ + default PrivateKey getPrivateKey(StringBuilder privateKeyId) { + return null; + } + + /** + * Retrieve server's corresponding public key in PEM format. + * @return public key in PEM format + */ + default String getPEMPublicKey() { + return null; + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/PrivateKeyStoreFactory.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/PrivateKeyStoreFactory.java new file mode 100644 index 00000000000..9812795cb9e --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/PrivateKeyStoreFactory.java @@ -0,0 +1,26 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.pkey; + +public interface PrivateKeyStoreFactory { + + /** + * Create and return a new PrivateKeyStore instance + * @param serverHostName hostname of the ZMS Server instance + * @return PrivateKeyStore instance + */ + public PrivateKeyStore create(String serverHostName); +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStore.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStore.java new file mode 100644 index 00000000000..b5cd9f98b35 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStore.java @@ -0,0 +1,131 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.pkey.file; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.PrivateKey; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.zms.ResourceException; +import com.yahoo.athenz.zms.ZMSConsts; +import com.yahoo.athenz.zms.pkey.PrivateKeyStore; + +public class FilePrivateKeyStore implements PrivateKeyStore { + + private static final Logger LOG = LoggerFactory.getLogger(FilePrivateKeyStore.class); + + String rootDir = null; + + public FilePrivateKeyStore() { + + // get the system root directory + + rootDir = System.getenv(ZMSConsts.STR_ENV_ROOT); + if (rootDir == null) { + rootDir = ZMSConsts.STR_DEF_ROOT; + } + } + + @Override + public PrivateKey getPrivateKey(StringBuilder privateKeyId) { + String privKeyName = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY, + rootDir + "/share/athenz/sys.auth/zms.key"); + + if (LOG.isDebugEnabled()) { + LOG.debug("FilePrivateKeyStore: private key file=" + privKeyName); + } + + // check to see if this is running in dev mode and thus it's + // a resource in our jar file + + String privKey = null; + if (privKeyName.startsWith(ZMSConsts.STR_JAR_RESOURCE)) { + privKey = retrieveKeyFromResource(privKeyName.substring(ZMSConsts.STR_JAR_RESOURCE.length())); + } else { + File privKeyFile = new File(privKeyName); + privKey = Crypto.encodedFile(privKeyFile); + } + + PrivateKey pkey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + + if (pkey == null) { + throw new ResourceException(500, "Unable to retrieve ZMS Server private key"); + } + + privateKeyId.append(System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_ID, "0")); + return pkey; + } + + @Override + public String getPEMPublicKey() { + + String pubKeyName = System.getProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY, + rootDir + "/share/athenz/pubkeys/zms.key"); + + if (LOG.isDebugEnabled()) { + LOG.debug("FilePrivateKeyStore: public key file=" + pubKeyName); + } + + // check to see if this is running in dev mode and thus it's + // a resource in our jar file + + String pubKey = null; + if (pubKeyName.startsWith(ZMSConsts.STR_JAR_RESOURCE)) { + pubKey = retrieveKeyFromResource(pubKeyName.substring(ZMSConsts.STR_JAR_RESOURCE.length())); + } else { + File pubKeyFile = new File(pubKeyName); + pubKey = Crypto.encodedFile(pubKeyFile); + } + + return pubKey; + } + + String retrieveKeyFromResource(String resourceName) { + + String key = null; + try (InputStream is = getClass().getResourceAsStream(resourceName)) { + String resourceData = getString(is); + if (resourceData != null) { + key = Crypto.ybase64(resourceData.getBytes("UTF-8")); + } + } catch (IOException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("FilePrivateKeyStore: Unable to read key from resource: " + resourceName); + } + } + + return key; + } + + String getString(InputStream is) throws IOException { + + if (is == null) { + return null; + } + + int ch; + StringBuilder sb = new StringBuilder(); + while ((ch = is.read()) != -1) { + sb.append((char) ch); + } + return sb.toString(); + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStoreFactory.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStoreFactory.java new file mode 100644 index 00000000000..e1f67458dbb --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStoreFactory.java @@ -0,0 +1,27 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.pkey.file; + +import com.yahoo.athenz.zms.pkey.PrivateKeyStore; +import com.yahoo.athenz.zms.pkey.PrivateKeyStoreFactory; + +public class FilePrivateKeyStoreFactory implements PrivateKeyStoreFactory { + + @Override + public PrivateKeyStore create(String serverHostName) { + return new FilePrivateKeyStore(); + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/AthenzDomain.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/AthenzDomain.java new file mode 100644 index 00000000000..1fc0d8acf57 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/AthenzDomain.java @@ -0,0 +1,80 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store; + +import java.util.ArrayList; +import java.util.List; + +import com.yahoo.athenz.zms.Domain; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.ServiceIdentity; + +public class AthenzDomain { + + String name; + List roles; + List policies; + List services; + Domain domain = null; + + public AthenzDomain(String name) { + this.name = name; + roles = new ArrayList<>(); + policies = new ArrayList<>(); + services = new ArrayList<>(); + } + + public void setName(String name) { + this.name = name; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public void setServices(List services) { + this.services = services; + } + + public void setPolicies(List policies) { + this.policies = policies; + } + + public void setDomain(Domain domain) { + this.domain = domain; + } + + public String getName() { + return name; + } + + public List getRoles() { + return roles; + } + + public List getPolicies() { + return policies; + } + + public List getServices() { + return services; + } + + public Domain getDomain() { + return domain; + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStore.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStore.java new file mode 100644 index 00000000000..56fe6ebbf36 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStore.java @@ -0,0 +1,22 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store; + +public interface ObjectStore { + + ObjectStoreConnection getConnection(boolean autoCommit); + void clearConnections(); +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStoreConnection.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStoreConnection.java new file mode 100644 index 00000000000..30e4c39323b --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/ObjectStoreConnection.java @@ -0,0 +1,117 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store; + +import java.io.Closeable; +import java.util.List; + +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.Domain; +import com.yahoo.athenz.zms.DomainModifiedList; +import com.yahoo.athenz.zms.Entity; +import com.yahoo.athenz.zms.Membership; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.PublicKeyEntry; +import com.yahoo.athenz.zms.ResourceAccessList; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.RoleAuditLog; +import com.yahoo.athenz.zms.ServiceIdentity; + +public interface ObjectStoreConnection extends Closeable { + + // Transaction commands + + void commitChanges(); + void rollbackChanges(); + void close(); + + // Domain commands + + Domain getDomain(String domainName); + boolean insertDomain(Domain domain); + boolean updateDomain(Domain domain); + boolean deleteDomain(String domainName); + long getDomainModTimestamp(String domainName); + boolean updateDomainModTimestamp(String domainName); + List listDomains(String prefix, long modifiedSince); + String lookupDomainById(String account, int productId); + List lookupDomainByRole(String roleMember, String roleName); + + AthenzDomain getAthenzDomain(String domainName); + DomainModifiedList listModifiedDomains(long modifiedSince); + + // Template commands + + boolean insertDomainTemplate(String domainName, String templateName, String params); + boolean deleteDomainTemplate(String domainName, String templateName, String params); + List listDomainTemplates(String domainName); + + // Role commands + + Role getRole(String domainName, String roleName); + boolean insertRole(String domainName, Role role); + boolean updateRole(String domainName, Role role); + boolean deleteRole(String domainName, String roleName); + boolean updateRoleModTimestamp(String domainName, String roleName); + List listRoles(String domainName); + List listRoleAuditLogs(String domainName, String roleName); + + List listRoleMembers(String domainName, String roleName); + Membership getRoleMember(String domainName, String roleName, String member); + boolean insertRoleMember(String domainName, String roleName, String member, String principal, String auditRef); + boolean deleteRoleMember(String domainName, String roleName, String member, String principal, String auditRef); + + // Policy commands + + Policy getPolicy(String domainName, String policyName); + boolean insertPolicy(String domainName, Policy policy); + boolean updatePolicy(String domainName, Policy policy); + boolean deletePolicy(String domainName, String policyName); + List listPolicies(String domainName, String assertionRoleName); + boolean updatePolicyModTimestamp(String domainName, String policyName); + + Assertion getAssertion(String domainName, String policyName, Long assertionId); + boolean insertAssertion(String domainName, String policyName, Assertion assertion); + boolean deleteAssertion(String domainName, String policyName, Long assertionId); + List listAssertions(String domainName, String policyName); + ResourceAccessList listResourceAccess(String principal, String action, String userDomain); + + // Service commands + + ServiceIdentity getServiceIdentity(String domainName, String serviceName); + boolean insertServiceIdentity(String domainName, ServiceIdentity service); + boolean updateServiceIdentity(String domainName, ServiceIdentity service); + boolean deleteServiceIdentity(String domainName, String serviceName); + List listServiceIdentities(String domainName); + + PublicKeyEntry getPublicKeyEntry(String domainName, String serviceName, String keyId); + boolean insertPublicKeyEntry(String domainName, String serviceName, PublicKeyEntry publicKey); + boolean updatePublicKeyEntry(String domainName, String serviceName, PublicKeyEntry publicKey); + boolean deletePublicKeyEntry(String domainName, String serviceName, String keyId); + List listPublicKeys(String domainName, String serviceName); + + List listServiceHosts(String domainName, String serviceName); + boolean insertServiceHost(String domainName, String serviceName, String hostName); + boolean deleteServiceHost(String domainName, String serviceName, String hostName); + + // Entity commands + + Entity getEntity(String domainName, String entityName); + boolean insertEntity(String domainName, Entity entity); + boolean updateEntity(String domainName, Entity entity); + boolean deleteEntity(String domainName, String entityName); + List listEntities(String domainName); +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/DomainStruct.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/DomainStruct.java new file mode 100644 index 00000000000..1c8cd8e67ad --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/DomainStruct.java @@ -0,0 +1,96 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.file; + +import java.util.ArrayList; +import java.util.HashMap; + +import com.yahoo.athenz.zms.DomainMeta; +import com.yahoo.athenz.zms.Entity; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.ServiceIdentity; +import com.yahoo.rdl.Timestamp; +import com.yahoo.rdl.UUID; + +public class DomainStruct { + + private String name; + private DomainMeta meta; + private UUID id; + private Timestamp modified; + private HashMap roles; + private HashMap policies; + private HashMap services; + private HashMap entities; + private ArrayList templates; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public DomainMeta getMeta() { + return meta; + } + public void setMeta(DomainMeta meta) { + this.meta = meta; + } + public UUID getId() { + return id; + } + public void setId(UUID id) { + this.id = id; + } + public Timestamp getModified() { + return modified; + } + public void setModified(Timestamp modified) { + this.modified = modified; + } + public HashMap getRoles() { + return roles; + } + public void setRoles(HashMap roles) { + this.roles = roles; + } + public HashMap getPolicies() { + return policies; + } + public void setPolicies(HashMap policies) { + this.policies = policies; + } + public HashMap getServices() { + return services; + } + public void setServices(HashMap services) { + this.services = services; + } + public HashMap getEntities() { + return entities; + } + public void setEntities(HashMap entities) { + this.entities = entities; + } + public ArrayList getTemplates() { + return templates; + } + public void setTemplates(ArrayList templates) { + this.templates = templates; + } + +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/FileConnection.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/FileConnection.java new file mode 100644 index 00000000000..cddfd6680a7 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/FileConnection.java @@ -0,0 +1,1197 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.file; + +import java.util.List; +import java.util.Set; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; + +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.AssertionEffect; +import com.yahoo.athenz.zms.Domain; +import com.yahoo.athenz.zms.DomainMeta; +import com.yahoo.athenz.zms.DomainModified; +import com.yahoo.athenz.zms.DomainModifiedList; +import com.yahoo.athenz.zms.Entity; +import com.yahoo.athenz.zms.Membership; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.PublicKeyEntry; +import com.yahoo.athenz.zms.ResourceAccessList; +import com.yahoo.athenz.zms.ResourceException; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.RoleAuditLog; +import com.yahoo.athenz.zms.ServiceIdentity; +import com.yahoo.athenz.zms.store.AthenzDomain; +import com.yahoo.athenz.zms.store.ObjectStoreConnection; +import com.yahoo.athenz.zms.utils.ZMSUtils; +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Timestamp; + +public class FileConnection implements ObjectStoreConnection { + + File rootDir; + public FileConnection(File rootDir) { + this.rootDir = rootDir; + } + + @Override + public void commitChanges() { + } + + @Override + public void rollbackChanges() { + } + + @Override + public void close() { + } + + @Override + public Domain getDomain(String domainName) { + + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + return null; + } + return getDomain(domainStruct); + } + + private Domain getDomain(DomainStruct domainStruct) { + Domain domain = new Domain() + .setAccount(domainStruct.getMeta().getAccount()) + .setDescription(domainStruct.getMeta().getDescription()) + .setId(domainStruct.getId()) + .setModified(domainStruct.getModified()) + .setName(domainStruct.getName()) + .setOrg(domainStruct.getMeta().getOrg()) + .setYpmId(domainStruct.getMeta().getYpmId()); + if (domainStruct.getMeta().getAuditEnabled() != null) { + domain.setAuditEnabled(domainStruct.getMeta().getAuditEnabled()); + } else { + domain.setAuditEnabled(false); + } + if (domainStruct.getMeta().getEnabled() != null) { + domain.setEnabled(domainStruct.getMeta().getEnabled()); + } else { + domain.setEnabled(true); + } + return domain; + } + + @Override + public long getDomainModTimestamp(String domainName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + return 0; + } + return domainStruct.getModified().millis(); + } + + @Override + public boolean insertDomain(Domain domain) { + + DomainStruct domainStruct = getDomainStruct(domain.getName()); + if (domainStruct != null) { + return false; + } + + verifyDomainProductIdUniqueness(domain.getName(), domain.getYpmId(), "insertDomain"); + + domainStruct = new DomainStruct(); + domainStruct.setId(domain.getId()); + domainStruct.setName(domain.getName()); + domainStruct.setModified(Timestamp.fromCurrentTime()); + + DomainMeta meta = new DomainMeta() + .setAccount(domain.getAccount()) + .setAuditEnabled(domain.getAuditEnabled()) + .setDescription(domain.getDescription()) + .setEnabled(domain.getEnabled()) + .setOrg(domain.getOrg()) + .setYpmId(domain.getYpmId()); + domainStruct.setMeta(meta); + + putDomainStruct(domain.getName(), domainStruct); + return true; + } + + void verifyDomainProductIdUniqueness(String name, Integer productId, String caller) { + + if (productId == null || productId.intValue() == 0) { + return; + } + String domName = lookupDomainById(null, productId); + if (domName != null && !domName.equals(name)) { + throw ZMSUtils.requestError("Product Id: " + productId + + " is already assigned to domain: " + domName, caller); + } + } + + @Override + public boolean updateDomain(Domain domain) { + + DomainStruct domainStruct = getDomainStruct(domain.getName()); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "updateDomain"); + } + + verifyDomainProductIdUniqueness(domain.getName(), domain.getYpmId(), "updateDomain"); + + domainStruct.setId(domain.getId()); + domainStruct.setName(domain.getName()); + domainStruct.setModified(Timestamp.fromCurrentTime()); + DomainMeta meta = new DomainMeta() + .setAccount(domain.getAccount()) + .setAuditEnabled(domain.getAuditEnabled()) + .setDescription(domain.getDescription()) + .setEnabled(domain.getEnabled()) + .setOrg(domain.getOrg()) + .setYpmId(domain.getYpmId()); + domainStruct.setMeta(meta); + + putDomainStruct(domain.getName(), domainStruct); + return true; + } + + @Override + public boolean deleteDomain(String domainName) { + File domainFile = new File(rootDir, domainName); + delete(domainFile); + return true; + } + + @Override + public boolean updateDomainModTimestamp(String domainName) { + + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "updateDomainModTimestamp"); + } + domainStruct.setModified(Timestamp.fromCurrentTime()); + putDomainStruct(domainName, domainStruct); + return true; + } + + @Override + public List listDomains(String prefix, long modifiedSince) { + + String[] fnames = rootDir.list(); + List slist = new ArrayList<>(java.util.Arrays.asList(fnames)); + java.util.Collections.sort(slist); + List domainList = new ArrayList<>(); + for (String name : slist) { + if (prefix != null) { + if (name.startsWith(prefix)) { + domainList.add(name); + } + } else { + domainList.add(name); + } + } + return domainList; + } + + @Override + public List lookupDomainByRole(String roleMember, String roleName) { + + boolean memberPresent = (roleMember != null && !roleMember.isEmpty()); + boolean rolePresent = (roleName != null && !roleName.isEmpty()); + + // first get the list of domains + + Set uniqueDomains = new HashSet<>(); + List domainNames = listDomains(null, 0); + for (String domainName : domainNames) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + continue; + } + if (rolePresent || memberPresent) { + if (rolePresent) { + Role role = getRoleObject(domainStruct, roleName); + if (role == null) { + continue; + } + if (memberPresent) { + if (role.getMembers().contains(roleMember)) { + uniqueDomains.add(domainName); + } + } else { + uniqueDomains.add(domainName); + } + } else { + HashMap roles = domainStruct.getRoles(); + if (roles != null) { + for (Role role : roles.values()) { + if (role.getMembers().contains(roleMember)) { + uniqueDomains.add(domainName); + break; + } + } + } + + } + } else { + uniqueDomains.add(domainName); + } + } + + List matchedDomains = new ArrayList<>(uniqueDomains); + Collections.sort(matchedDomains); + return matchedDomains; + } + + @Override + public String lookupDomainById(String account, int productId) { + + // first get the list of domains + + List domainNames = listDomains(null, 0); + for (String domainName : domainNames) { + Domain domain = getDomain(domainName); + if (domain == null) { + continue; + } + if (account != null) { + if (domain.getAccount() != null && account.equals(domain.getAccount())) { + return domainName; + } + } else if (productId != 0) { + if (domain.getYpmId() != null && domain.getYpmId() == productId) { + return domainName; + } + } + } + + return null; + } + + @Override + public AthenzDomain getAthenzDomain(String domainName) { + + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "getAthenzDomain"); + } + AthenzDomain athenzDomain = new AthenzDomain(domainName); + athenzDomain.setDomain(getDomain(domainStruct)); + if (domainStruct.getRoles() != null) { + athenzDomain.setRoles(new ArrayList(domainStruct.getRoles().values())); + } + if (domainStruct.getPolicies() != null) { + athenzDomain.setPolicies(new ArrayList(domainStruct.getPolicies().values())); + } + if (domainStruct.getServices() != null) { + athenzDomain.setServices(new ArrayList(domainStruct.getServices().values())); + } + + return athenzDomain; + } + + @Override + public DomainModifiedList listModifiedDomains(long modifiedSince) { + + DomainModifiedList domainModifiedList = new DomainModifiedList(); + List nameMods = new ArrayList(); + + List domainList = listDomains(null, modifiedSince); + + // Now set the dest for the returned domain names + + String dirName = rootDir.getAbsolutePath() + File.separator; + for (String dname : domainList) { + long ts = 0; + try { + File dfile = new File(dirName + dname); + ts = dfile.lastModified(); + if (ts <= modifiedSince) { + continue; + } + } catch (Exception exc) { + System.out.println("FileStructStore: FAILED to get timestamp for file " + dname); + exc.printStackTrace(); + } + DomainModified dm = new DomainModified().setName(dname).setModified(ts); + nameMods.add(dm); + } + + domainModifiedList.setNameModList(nameMods); + return domainModifiedList; + } + + @Override + public boolean insertDomainTemplate(String domainName, String templateName, String params) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "insertDomainTemplate"); + } + + if (domainStruct.getTemplates() == null) { + domainStruct.setTemplates(new ArrayList()); + } + ArrayList templates = domainStruct.getTemplates(); + if (!templates.contains(templateName)) { + templates.add(templateName); + } + putDomainStruct(domainName, domainStruct); + return true; + } + + @Override + public boolean deleteDomainTemplate(String domainName, String templateName, String params) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "deleteDomainTemplate"); + } + ArrayList templates = domainStruct.getTemplates(); + if (templates == null) { + return true; + } + templates.remove(templateName); + putDomainStruct(domainName, domainStruct); + return true; + } + + @Override + public List listDomainTemplates(String domainName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "listDomainTemplates"); + } + ArrayList list = new ArrayList<>(); + if (domainStruct.getTemplates() != null) { + list.addAll(domainStruct.getTemplates()); + } + return list; + } + + Role getRoleObject(DomainStruct domain, String roleName) { + HashMap roles = domain.getRoles(); + if (roles == null) { + return null; + } + return roles.get(roleName); + } + + Policy getPolicyObject(DomainStruct domain, String policyName) { + HashMap policies = domain.getPolicies(); + if (policies == null) { + return null; + } + return policies.get(policyName); + } + + ServiceIdentity getServiceObject(DomainStruct domain, String serviceName) { + HashMap services = domain.getServices(); + if (services == null) { + return null; + } + return services.get(serviceName); + } + + Entity getEntityObject(DomainStruct domain, String entityName) { + HashMap entities = domain.getEntities(); + if (entities == null) { + return null; + } + return entities.get(entityName); + } + + @Override + public Role getRole(String domainName, String roleName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "getRole"); + } + return getRoleObject(domainStruct, roleName); + } + + @Override + public boolean insertRole(String domainName, Role role) { + updateRole(domainName, role); + return true; + } + + @Override + public boolean updateRole(String domainName, Role role) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "updateRole"); + } + if (domainStruct.getRoles() == null) { + domainStruct.setRoles(new HashMap()); + } + HashMap roles = domainStruct.getRoles(); + + String roleName = extractRoleName(domainName, role.getName()); + if (roleName == null) { + throw ZMSUtils.error(ResourceException.BAD_REQUEST, "invalid role name", "updateRole"); + } + + // here we only need to update the main attrs and not + // the members + + Role originalRole = getRoleObject(domainStruct, roleName); + List members = role.getMembers(); + if (originalRole != null) { + role.setMembers(originalRole.getMembers()); + } else { + role.setMembers(null); + } + role.setModified(Timestamp.fromCurrentTime()); + roles.put(roleName, role); + putDomainStruct(domainName, domainStruct); + role.setMembers(members); + return true; + } + + @Override + public boolean updateRoleModTimestamp(String domainName, String roleName) { + + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "updateRoleModTimestamp"); + } + Role role = getRoleObject(domainStruct, roleName); + role.setModified(Timestamp.fromCurrentTime()); + putDomainStruct(domainName, domainStruct); + return true; + } + + @Override + public boolean deleteRole(String domainName, String roleName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "deleteRole"); + } + HashMap roles = domainStruct.getRoles(); + + if (roles != null) { + roles.remove(roleName); + putDomainStruct(domainName, domainStruct); + } + return true; + } + + @Override + public List listEntities(String domainName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "listEntities"); + } + HashMap entities = domainStruct.getEntities(); + ArrayList list = new ArrayList<>(); + if (entities != null) { + list.addAll(entities.keySet()); + } + Collections.sort(list); + return list; + } + + @Override + public List listRoles(String domainName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "listRoles"); + } + HashMap roles = domainStruct.getRoles(); + ArrayList list = new ArrayList<>(); + if (roles != null) { + list.addAll(roles.keySet()); + } + Collections.sort(list); + return list; + } + + @Override + public List listRoleMembers(String domainName, String roleName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "listRoleMembers"); + } + Role role = getRoleObject(domainStruct, roleName); + if (role == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "role not found", "listRoleMembers"); + } + return role.getMembers(); + } + + @Override + public Membership getRoleMember(String domainName, String roleName, String principal) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "getRoleMember"); + } + Role role = getRoleObject(domainStruct, roleName); + if (role == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "role not found", "getRoleMember"); + } + Membership membership = new Membership() + .setMemberName(principal) + .setRoleName(ZMSUtils.roleResourceName(domainName, roleName)) + .setIsMember(false); + if (role.getMembers() != null) { + Set members = new HashSet<>(role.getMembers()); + membership.setIsMember(members.contains(principal)); + } + return membership; + } + + @Override + public boolean insertRoleMember(String domainName, String roleName, String principal, + String admin, String auditRef) { + + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "insertRoleMember"); + } + Role role = getRoleObject(domainStruct, roleName); + if (role == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "role not found", "insertRoleMember"); + } + if (!validatePrincipalDomain(principal)) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "principal domain not found", "insertRoleMember"); + } + // make sure our existing role as the member array + // and if it doesn't exist then create one + if (role.getMembers() == null) { + role.setMembers(new ArrayList()); + } + role.getMembers().add(principal); + putDomainStruct(domainName, domainStruct); + return true; + } + + boolean validatePrincipalDomain(String principal) { + int idx = principal.lastIndexOf('.'); + if (idx == -1 || idx == 0 || idx == principal.length() - 1) { + return false; + } + if (getDomainStruct(principal.substring(0, idx)) == null) { + return false; + } + return true; + } + + @Override + public boolean deleteRoleMember(String domainName, String roleName, String principal, + String admin, String auditRef) { + + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "deleteRoleMember"); + } + Role role = getRoleObject(domainStruct, roleName); + if (role == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "role not found", "deleteRoleMember"); + } + if (role.getMembers() != null) { + role.getMembers().remove(principal); + } + putDomainStruct(domainName, domainStruct); + return true; + } + + @Override + public Policy getPolicy(String domainName, String policyName) { + + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "getPolicy"); + } + return getPolicyObject(domainStruct, policyName); + } + + @Override + public boolean insertPolicy(String domainName, Policy policy) { + return updatePolicy(domainName, policy); + } + + @Override + public boolean updatePolicy(String domainName, Policy policy) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "updatePolicy"); + } + + if (domainStruct.getPolicies() == null) { + domainStruct.setPolicies(new HashMap()); + } + HashMap policies = domainStruct.getPolicies(); + + String policyName = extractPolicyName(domainName, policy.getName()); + + // here we only need to update the main attrs and not + // the assertions + + List assertions = policy.getAssertions(); + Policy originalPolicy = getPolicyObject(domainStruct, policyName); + if (originalPolicy != null) { + policy.setAssertions(originalPolicy.getAssertions()); + } else { + policy.setAssertions(null); + } + policy.setModified(Timestamp.fromCurrentTime()); + policies.put(policyName, policy); + putDomainStruct(domainName, domainStruct); + policy.setAssertions(assertions); + return true; + } + + @Override + public boolean deletePolicy(String domainName, String policyName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "deletePolicy"); + } + if (domainStruct.getPolicies() != null) { + domainStruct.getPolicies().remove(policyName); + putDomainStruct(domainName, domainStruct); + } + return true; + } + + @Override + public List listPolicies(String domainName, String assertionRoleName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "listPolicies"); + } + ArrayList list = new ArrayList<>(); + if (assertionRoleName == null) { + HashMap policies = domainStruct.getPolicies(); + if (policies != null) { + list.addAll(policies.keySet()); + } + } else { + List assertions = null; + HashMap policies = domainStruct.getPolicies(); + for (Policy policy : policies.values()) { + assertions = policy.getAssertions(); + if (assertions == null) { + continue; + } + for (Assertion assertion : assertions) { + if (assertionRoleName.compareToIgnoreCase(assertion.getRole()) == 0) { + list.add(policy.getName()); + break; + } + } + } + } + Collections.sort(list); + return list; + } + + @Override + public boolean insertAssertion(String domainName, String policyName, Assertion assertion) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "insertAssertion"); + } + Policy policy = getPolicyObject(domainStruct, policyName); + if (policy == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "policy not found", "insertAssertion"); + } + List assertions = policy.getAssertions(); + if (assertions == null) { + assertions = new ArrayList<>(); + policy.setAssertions(assertions); + } + assertions.add(assertion); + assertion.setId(System.nanoTime()); + putDomainStruct(domainName, domainStruct); + return true; + } + + boolean assertionMatch(Assertion assertion1, Assertion assertion2) { + + if (!assertion1.getAction().equals(assertion2.getAction())) { + return false; + } + if (!assertion1.getResource().equals(assertion2.getResource())) { + return false; + } + if (!assertion1.getRole().equals(assertion2.getRole())) { + return false; + } + AssertionEffect effect1 = AssertionEffect.ALLOW; + if (assertion1.getEffect() != null) { + effect1 = assertion1.getEffect(); + } + AssertionEffect effect2 = AssertionEffect.ALLOW; + if (assertion2.getEffect() != null) { + effect2 = assertion2.getEffect(); + } + if (effect1 != effect2) { + return false; + } + return true; + } + + @Override + public boolean deleteAssertion(String domainName, String policyName, Long assertionId) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "deleteAssertion"); + } + Policy policy = getPolicyObject(domainStruct, policyName); + if (policy == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "policy not found", "insertAssertion"); + } + List assertions = policy.getAssertions(); + boolean deleted = false; + for (int i = 0; i < assertions.size(); i++) { + if (assertions.get(i).getId().equals(assertionId)) { + assertions.remove(i); + deleted = true; + break; + } + } + putDomainStruct(domainName, domainStruct); + return deleted; + } + + @Override + public List listAssertions(String domainName, String policyName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "listAssertions"); + } + Policy policy = getPolicyObject(domainStruct, policyName); + if (policy == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "policy not found", "listAssertions"); + } + return policy.getAssertions(); + } + + @Override + public ServiceIdentity getServiceIdentity(String domainName, String serviceName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "getServiceIdentity"); + } + return getServiceObject(domainStruct, serviceName); + } + + @Override + public boolean insertServiceIdentity(String domainName, ServiceIdentity service) { + return updateServiceIdentity(domainName, service); + } + + @Override + public boolean updateServiceIdentity(String domainName, ServiceIdentity service) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "updateServiceIdentity"); + } + + if (domainStruct.getServices() == null) { + domainStruct.setServices(new HashMap()); + } + HashMap services = domainStruct.getServices(); + + service.setModified(Timestamp.fromCurrentTime()); + String serviceName = extractServiceName(domainName, service.getName()); + + // here we only need to update the main attrs and not + // the public keys and hosts + + List publicKeys = service.getPublicKeys(); + List hosts = service.getHosts(); + ServiceIdentity originalService = getServiceObject(domainStruct, serviceName); + if (originalService != null) { + service.setPublicKeys(originalService.getPublicKeys()); + service.setHosts(originalService.getHosts()); + } else { + service.setPublicKeys(null); + service.setHosts(null); + } + service.setModified(Timestamp.fromCurrentTime()); + services.put(serviceName, service); + putDomainStruct(domainName, domainStruct); + service.setPublicKeys(publicKeys); + service.setHosts(hosts); + return true; + } + + @Override + public boolean deleteServiceIdentity(String domainName, String serviceName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "deleteServiceIdentity"); + } + HashMap services = domainStruct.getServices(); + if (services != null) { + services.remove(serviceName); + putDomainStruct(domainName, domainStruct); + } + return true; + } + + @Override + public List listServiceIdentities(String domainName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "listServiceIdentities"); + } + HashMap services = domainStruct.getServices(); + ArrayList list = new ArrayList<>(); + if (services != null) { + list.addAll(services.keySet()); + } + Collections.sort(list); + return list; + } + + @Override + public PublicKeyEntry getPublicKeyEntry(String domainName, String serviceName, String keyId) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "getPublicKeyEntry"); + } + ServiceIdentity service = getServiceObject(domainStruct, serviceName); + if (service == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "service not found", "getPublicKeyEntry"); + } + List publicKeys = service.getPublicKeys(); + if (publicKeys == null) { + return null; + } + for (PublicKeyEntry keyEntry : publicKeys) { + if (keyId.equals(keyEntry.getId())) { + return keyEntry; + } + } + return null; + } + + @Override + public boolean insertPublicKeyEntry(String domainName, String serviceName, PublicKeyEntry publicKey) { + return updatePublicKeyEntry(domainName, serviceName, publicKey); + } + + boolean removePublicKeyEntry(List keyList, String keyId) { + + if (keyList == null) { + return false; + } + + for (int idx = 0; idx < keyList.size(); idx++) { + if (keyId.equals(keyList.get(idx).getId())) { + keyList.remove(idx); + return true; + } + } + + return false; + } + + void updatePublicKeyEntry(ServiceIdentity service, PublicKeyEntry keyEntry) { + + // first we are going to remove the key from our array + // if one already exists. If the keyList is null, then + // we're going to create and set an empty list so + // later we can add the new key entry object to that list + + if (service.getPublicKeys() == null) { + service.setPublicKeys(new ArrayList()); + } + List keyList = service.getPublicKeys(); + removePublicKeyEntry(keyList, keyEntry.getId()); + + // now let's add our new key to the list + + keyList.add(keyEntry); + } + + @Override + public boolean updatePublicKeyEntry(String domainName, String serviceName, PublicKeyEntry publicKey) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "updatePublicKeyEntry"); + } + ServiceIdentity service = getServiceObject(domainStruct, serviceName); + if (service == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "service not found", "updatePublicKeyEntry"); + } + updatePublicKeyEntry(service, publicKey); + putDomainStruct(domainName, domainStruct); + return true; + } + + @Override + public boolean deletePublicKeyEntry(String domainName, String serviceName, String keyId) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "deletePublicKeyEntry"); + } + ServiceIdentity service = getServiceObject(domainStruct, serviceName); + if (service == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "service not found", "deletePublicKeyEntry"); + } + List keyList = service.getPublicKeys(); + boolean keyRemoved = removePublicKeyEntry(keyList, keyId); + if (!keyRemoved) { + return false; + } + putDomainStruct(domainName, domainStruct); + return true; + } + + @Override + public List listPublicKeys(String domainName, String serviceName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "listPublicKeys"); + } + ServiceIdentity service = getServiceObject(domainStruct, serviceName); + if (service == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "service not found", "deletePublicKeyEntry"); + } + return service.getPublicKeys(); + } + + @Override + public List listServiceHosts(String domainName, String serviceName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "listServiceHosts"); + } + ServiceIdentity service = getServiceObject(domainStruct, serviceName); + if (service == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "service not found", "deletePublicKeyEntry"); + } + return service.getHosts(); + } + + @Override + public boolean insertServiceHost(String domainName, String serviceName, String hostName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "insertServiceHost"); + } + ServiceIdentity service = getServiceObject(domainStruct, serviceName); + if (service == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "service not found", "insertServiceHost"); + } + if (service.getHosts() == null) { + service.setHosts(new ArrayList()); + } + List hosts = service.getHosts(); + hosts.add(hostName); + putDomainStruct(domainName, domainStruct); + return true; + } + + @Override + public boolean deleteServiceHost(String domainName, String serviceName, String hostName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "deleteServiceHost"); + } + ServiceIdentity service = getServiceObject(domainStruct, serviceName); + if (service == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "service not found", "deleteServiceHost"); + } + List hosts = service.getHosts(); + if (hosts == null) { + return false; + } + hosts.remove(hostName); + putDomainStruct(domainName, domainStruct); + return true; + } + + @Override + public Entity getEntity(String domainName, String entityName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "getEntity"); + } + return getEntityObject(domainStruct, entityName); + } + + @Override + public boolean insertEntity(String domainName, Entity entity) { + return updateEntity(domainName, entity); + } + + @Override + public boolean updateEntity(String domainName, Entity entity) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "updateEntity"); + } + if (domainStruct.getEntities() == null) { + domainStruct.setEntities(new HashMap()); + } + HashMap entities = domainStruct.getEntities(); + entities.put(entity.getName(), entity); + putDomainStruct(domainName, domainStruct); + return true; + } + + @Override + public boolean deleteEntity(String domainName, String entityName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "deleteEntity"); + } + HashMap entities = domainStruct.getEntities(); + if (entities != null) { + entities.remove(entityName); + putDomainStruct(domainName, domainStruct); + } + return true; + } + + public synchronized DomainStruct getDomainStruct(String name) { + File f = new File(rootDir, name); + if (!f.exists()) { + return null; + } + DomainStruct domainStruct = null; + try { + Path path = Paths.get(f.toURI()); + domainStruct = JSON.fromBytes(Files.readAllBytes(path), DomainStruct.class); + } catch (IOException e) { + e.printStackTrace(); + } + return domainStruct; + } + + public synchronized void putDomainStruct(String name, DomainStruct data) { + + File f = new File(rootDir, name); + String policydata = JSON.string(data); + try { + FileWriter fileWriter = new FileWriter(f); + fileWriter.write(policydata); + fileWriter.flush(); + fileWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void delete(File f) { + if (f.exists()) { + if (f.isDirectory()) { + + File[] fileList = f.listFiles(); + if (fileList != null) { + for (File ff : fileList) { + delete(ff); + } + } + } + if (!f.delete()) { + throw new RuntimeException("Cannot delete file: " + f); + } + } + } + + public static void deleteDirectory(File f) { + delete(f); + } + + public synchronized void delete(String name) { + File f = new File(rootDir, name); + delete(f); + } + + String extractObjectName(String domainName, String fullName, String objType) { + + // generate prefix to compare with + + StringBuilder prefixBuffer = new StringBuilder(512).append(domainName).append(objType); + String prefix = prefixBuffer.toString(); + if (!fullName.startsWith(prefix)) { + return null; + } + return fullName.substring(prefix.length()); + } + + String extractRoleName(String domainName, String fullRoleName) { + return extractObjectName(domainName, fullRoleName, ":role."); + } + + String extractPolicyName(String domainName, String fullPolicyName) { + return extractObjectName(domainName, fullPolicyName, ":policy."); + } + + String extractServiceName(String domainName, String fullServiceName) { + return extractObjectName(domainName, fullServiceName, "."); + } + + @Override + public List listRoleAuditLogs(String domainName, String roleName) { + List list = new ArrayList<>(); + return list; + } + + @Override + public ResourceAccessList listResourceAccess(String principal, String action, String userDomain) { + throw ZMSUtils.error(ResourceException.INTERNAL_SERVER_ERROR, "Not Implemented", "listResourceAccess"); + } + + @Override + public Assertion getAssertion(String domainName, String policyName, Long assertionId) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "getAssertion"); + } + Policy policy = getPolicyObject(domainStruct, policyName); + if (policy == null) { + return null; + } + List assertions = policy.getAssertions(); + if (assertions == null) { + return null; + } + for (Assertion assertion : assertions) { + if (assertion.getId().equals(assertionId)) { + return assertion; + } + } + return null; + } + + @Override + public boolean updatePolicyModTimestamp(String domainName, String policyName) { + DomainStruct domainStruct = getDomainStruct(domainName); + if (domainStruct == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "domain not found", "updatePolicyModTimestamp"); + } + Policy policy = getPolicyObject(domainStruct, policyName); + if (policy == null) { + throw ZMSUtils.error(ResourceException.NOT_FOUND, "policy not found", "updatePolicyModTimestamp"); + } + policy.setModified(Timestamp.fromCurrentTime()); + putDomainStruct(domainName, domainStruct); + return true; + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/FileObjectStore.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/FileObjectStore.java new file mode 100644 index 00000000000..63c6466c665 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/file/FileObjectStore.java @@ -0,0 +1,52 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.file; + +import java.io.File; + +import com.yahoo.athenz.zms.store.ObjectStore; +import com.yahoo.athenz.zms.store.ObjectStoreConnection; + +public class FileObjectStore implements ObjectStore { + + File rootDir; + + public FileObjectStore(File rootDirectory) { + if (!rootDirectory.exists()) { + if (!rootDirectory.mkdirs()) { + error("cannot create specified root: " + rootDirectory); + } + } else { + if (!rootDirectory.isDirectory()) { + error("specified root is not a directory: " + rootDirectory); + } + } + this.rootDir = rootDirectory; + } + + @Override + public ObjectStoreConnection getConnection(boolean autoCommit) { + return new FileConnection(rootDir); + } + + @Override + public void clearConnections() { + } + + static void error(String msg) { + throw new RuntimeException("FileObjectStore: " + msg); + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/DataSourceFactory.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/DataSourceFactory.java new file mode 100644 index 00000000000..79f4fee6cd1 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/DataSourceFactory.java @@ -0,0 +1,267 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.jdbc; + +import java.io.PrintWriter; +import java.util.concurrent.TimeUnit; +import java.sql.DriverManager; +import java.sql.Connection; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLException; + +import org.apache.commons.dbcp2.ConnectionFactory; +import org.apache.commons.dbcp2.DriverManagerConnectionFactory; +import org.apache.commons.dbcp2.PoolableConnection; +import org.apache.commons.dbcp2.PoolableConnectionFactory; +import org.apache.commons.pool2.ObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.apache.commons.pool2.impl.BaseObjectPoolConfig; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// a simple factory for datasources +// typically, the datasource configuration would be done by the container (i.e. spring) +public class DataSourceFactory { + + private static final Logger LOG = LoggerFactory.getLogger(DataSourceFactory.class); + + static final String ZMS_PROP_DBUSER = "athenz.zms.jdbc_user"; + static final String ZMS_PROP_DBPASS = "athenz.zms.jdbc_password"; + + static final String ZMS_PROP_DBPOOL_MAX_TOTAL = "athenz.zms.db_pool_max_total"; + static final String ZMS_PROP_DBPOOL_MAX_IDLE = "athenz.zms.db_pool_max_idle"; + static final String ZMS_PROP_DBPOOL_MIN_IDLE = "athenz.zms.db_pool_min_idle"; + static final String ZMS_PROP_DBPOOL_MAX_WAIT = "athenz.zms.db_pool_max_wait"; + static final String ZMS_PROP_DBPOOL_EVICT_IDLE_TIMEOUT = "athenz.zms.db_pool_evict_idle_timeout"; + static final String ZMS_PROP_DBPOOL_EVICT_IDLE_INTERVAL = "athenz.zms.db_pool_evict_idle_interval"; + static final String ZMS_PROP_DBPOOL_MAX_TTL = "athenz.zms.db_pool_max_ttl"; + + static final long MAX_TTL_CONN_MS = TimeUnit.MILLISECONDS.convert(10L, TimeUnit.MINUTES); + + public static PoolableDataSource create(String url) { + String userName = System.getProperty(ZMS_PROP_DBUSER); + String driver = "?"; + try { + if (url.indexOf("sqlite") > 0) { + driver = "org.sqlite.JDBC"; + Class.forName(driver); + return new SimpleDataSource(driver, url); + } else if (url.indexOf(":mysql:") > 0) { + driver = "com.mysql.jdbc.Driver"; + Class.forName(driver); + + String password = System.getProperty(ZMS_PROP_DBPASS, ""); + ConnectionFactory connectionFactory = + new DriverManagerConnectionFactory(url, userName, password); + + return create(connectionFactory); + } else { + throw new RuntimeException("Cannot figure out how to instantiate this data source: " + url); + } + } catch (ClassNotFoundException e) { + throw new RuntimeException("Cannot load driver class: " + driver); + } catch (Exception exc) { + throw new RuntimeException("Failed to create database source(" + + url + ") with driver(" + driver + ")", exc); + } + } + + static long retrieveConfigSetting(String propName, long defaultValue) { + + final String errPrefix = "Using default instead. Ignoring Invalid number("; + + String propValue = System.getProperty(propName); + if (propValue == null) { + return defaultValue; + } + + long value = defaultValue; + try { + value = Long.parseLong(propValue); + } catch (NumberFormatException ex) { + if (LOG.isWarnEnabled()) { + LOG.warn(errPrefix + propValue + ") set in system property(" + propName + "): " + ex.getMessage()); + } + } + + return value; + } + + static int retrieveConfigSetting(String propName, int defaultValue) { + + final String errPrefix = "Using default instead. Ignoring Invalid number("; + + String propValue = System.getProperty(propName); + if (propValue == null) { + return defaultValue; + } + + int value = defaultValue; + try { + value = Integer.parseInt(propValue); + } catch (NumberFormatException ex) { + if (LOG.isWarnEnabled()) { + LOG.warn(errPrefix + propValue + ") set in system property(" + propName + "): " + ex.getMessage()); + } + } + + return value; + } + + public static GenericObjectPoolConfig setupPoolConfig() { + + // setup config vars for the object pool + // ie. min and max idle instances, and max total instances of arbitrary objects + + GenericObjectPoolConfig config = new GenericObjectPoolConfig(); + + // The maximum number of active connections that can be allocated from + // this pool at the same time, or negative for no limit. Default: 8 + config.setMaxTotal(retrieveConfigSetting(ZMS_PROP_DBPOOL_MAX_TOTAL, GenericObjectPoolConfig.DEFAULT_MAX_TOTAL)); + if (config.getMaxTotal() == 0) { + config.setMaxTotal(-1); // -1 means no limit + } + + // The maximum number of connections that can remain idle in the pool, + // without extra ones being released, or negative for no limit. Default 8 + config.setMaxIdle(retrieveConfigSetting(ZMS_PROP_DBPOOL_MAX_IDLE, GenericObjectPoolConfig.DEFAULT_MAX_IDLE)); + if (config.getMaxIdle() == 0) { + config.setMaxIdle(-1); // -1 means no limit + } + + // The minimum number of connections that can remain idle in the pool, + // without extra ones being created, or zero to create none. Default 0 + config.setMinIdle(retrieveConfigSetting(ZMS_PROP_DBPOOL_MIN_IDLE, GenericObjectPoolConfig.DEFAULT_MIN_IDLE)); + + // The maximum number of milliseconds that the pool will wait (when + // there are no available connections) for a connection to be returned + // before throwing an exception, or -1 to wait indefinitely. Default -1 + config.setMaxWaitMillis(retrieveConfigSetting(ZMS_PROP_DBPOOL_MAX_WAIT, + GenericObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS)); + + // setup the configuration to cleanup idle connections + // + // Minimum time an object can be idle in the pool before being eligible + // for eviction by the idle object evictor. + // The default value is 30 minutes (1000 * 60 * 30). + config.setMinEvictableIdleTimeMillis(retrieveConfigSetting(ZMS_PROP_DBPOOL_EVICT_IDLE_TIMEOUT, + BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS)); + + // Number of milliseconds to sleep between runs of idle object evictor thread. + // Not using DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS since it is -1 + // meaning it will not run the evictor thread and instead we're using + // the default min value for evictable idle connections (Default 30 minutes) + config.setTimeBetweenEvictionRunsMillis(retrieveConfigSetting(ZMS_PROP_DBPOOL_EVICT_IDLE_INTERVAL, + BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS)); + + if (LOG.isDebugEnabled()) { + LOG.debug("Config settings for idle object eviction: " + + "time interval between eviction thread runs(" + + config.getTimeBetweenEvictionRunsMillis() + + " millis): minimum timeout for idle objects(" + + config.getMinEvictableIdleTimeMillis() + " millis)"); + } + + // Validate objects by the idle object evictor. If invalid, gets dropped + // from the pool. + config.setTestWhileIdle(true); + + // Validate object before borrowing from pool. If invalid, gets dropped + // from the pool and an attempt to borrow another one will occur. + config.setTestOnBorrow(true); + return config; + } + + static PoolableDataSource create(ConnectionFactory connectionFactory) { + + // setup our pool config object + + GenericObjectPoolConfig config = setupPoolConfig(); + + PoolableConnectionFactory poolableConnectionFactory = + new PoolableConnectionFactory(connectionFactory, null); + + // Set max lifetime of a connection in milli-secs, after which it will + // always fail activation, passivation, and validation. + // Value of -1 means infinite life time. The default value + // defined in this class is 10 minutes. + long connTtlMillis = retrieveConfigSetting(ZMS_PROP_DBPOOL_MAX_TTL, MAX_TTL_CONN_MS); + poolableConnectionFactory.setMaxConnLifetimeMillis(connTtlMillis); + if (LOG.isInfoEnabled()) { + LOG.info("Setting Time-To-Live interval for live connections(" + + connTtlMillis + ")milli-secs"); + } + + ObjectPool connectionPool = new GenericObjectPool<>(poolableConnectionFactory, config); + poolableConnectionFactory.setPool(connectionPool); + + ZMSDataSource dataSource = new ZMSDataSource(connectionPool); + return dataSource; + } + + static class SimpleDataSource implements PoolableDataSource { + PrintWriter logger; + String className; + String url; + + SimpleDataSource(String className, String url) { + this.className = className; + this.url = url; + this.logger = new PrintWriter(System.out); + } + + public Connection getConnection() { + try { + Class.forName(className); + return DriverManager.getConnection(url); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Cannot load class: " + className); + } catch (SQLException e) { + throw new RuntimeException("Cannot connect to database " + url); + } + } + public Connection getConnection(String user, String pass) { + return getConnection(); //use a real datasource if you want this + } + public int getLoginTimeout() { + return 0; + } + public void setLoginTimeout(int seconds) { + } + public void setLogWriter(PrintWriter out) { + logger = out; + } + public PrintWriter getLogWriter() { + return logger; + } + public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(); + } + public boolean isWrapperFor(Class iface) { + return false; + } + public T unwrap(Class iface) throws SQLException { + throw new SQLException("Not implemented"); + } + + @Override + public void clearPoolConnections() { + } + } +} + diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/JDBCConnection.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/JDBCConnection.java new file mode 100644 index 00000000000..1c855ffe420 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/JDBCConnection.java @@ -0,0 +1,2987 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.AssertionEffect; +import com.yahoo.athenz.zms.Domain; +import com.yahoo.athenz.zms.DomainModified; +import com.yahoo.athenz.zms.DomainModifiedList; +import com.yahoo.athenz.zms.Entity; +import com.yahoo.athenz.zms.Membership; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.PublicKeyEntry; +import com.yahoo.athenz.zms.ResourceAccess; +import com.yahoo.athenz.zms.ResourceAccessList; +import com.yahoo.athenz.zms.ResourceException; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.RoleAuditLog; +import com.yahoo.athenz.zms.ServiceIdentity; +import com.yahoo.athenz.zms.ZMSConsts; +import com.yahoo.athenz.zms.store.AthenzDomain; +import com.yahoo.athenz.zms.store.ObjectStoreConnection; +import com.yahoo.athenz.zms.utils.ZMSUtils; +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Struct; +import com.yahoo.rdl.Timestamp; +import com.yahoo.rdl.UUID; + +public class JDBCConnection implements ObjectStoreConnection { + + private static final Logger LOG = LoggerFactory.getLogger(JDBCConnection.class); + + private static final int MYSQL_ER_OPTION_PREVENTS_STATEMENT = 1290; + private static final int MYSQL_ER_OPTION_DUPLICATE_ENTRY = 1062; + + private static final String SQL_DELETE_DOMAIN = "DELETE FROM domain WHERE name=?;"; + private static final String SQL_GET_DOMAIN = "SELECT * FROM domain WHERE name=?;"; + private static final String SQL_GET_DOMAIN_ID = "SELECT domain_id FROM domain WHERE name=?;"; + private static final String SQL_GET_DOMAIN_WITH_ACCOUNT = "SELECT name FROM domain WHERE account=?;"; + private static final String SQL_GET_DOMAIN_WITH_PRODUCT_ID = "SELECT name FROM domain WHERE ypm_id=?;"; + private static final String SQL_INSERT_DOMAIN = "INSERT INTO domain " + + "(name, description, org, uuid, enabled, audit_enabled, account, ypm_id) VALUES (?,?,?,?,?,?,?,?);"; + private static final String SQL_UPDATE_DOMAIN = "UPDATE domain " + + "SET description=?, org=?, uuid=?, enabled=?, audit_enabled=?, account=?, ypm_id=? WHERE name=?;"; + private static final String SQL_UPDATE_DOMAIN_MOD_TIMESTAMP = "UPDATE domain " + + "SET modified=CURRENT_TIMESTAMP(3) WHERE name=?;"; + private static final String SQL_GET_DOMAIN_MOD_TIMESTAMP = "SELECT modified FROM domain WHERE name=?;"; + private static final String SQL_LIST_DOMAIN = "SELECT name, modified FROM domain;"; + private static final String SQL_LIST_DOMAIN_PREFIX = "SELECT name, modified FROM domain WHERE name>=? AND name?;"; + private static final String SQL_LIST_DOMAIN_PREFIX_MODIFIED = "SELECT name, modified FROM domain " + + "WHERE name>=? AND name?;"; + private static final String SQL_LIST_DOMAIN_ROLE_NAME_MEMBER = "SELECT domain.name FROM domain " + + "JOIN role ON role.domain_id=domain.domain_id " + + "JOIN role_member ON role_member.role_id=role.role_id " + + "JOIN principal ON principal.principal_id=role_member.principal_id " + + "WHERE principal.name=? AND role.name=?;"; + private static final String SQL_LIST_DOMAIN_ROLE_MEMBER = "SELECT domain.name FROM domain " + + "JOIN role ON role.domain_id=domain.domain_id " + + "JOIN role_member ON role_member.role_id=role.role_id " + + "JOIN principal ON principal.principal_id=role_member.principal_id " + + "WHERE principal.name=?;"; + private static final String SQL_LIST_DOMAIN_ROLE_NAME = "SELECT domain.name FROM domain " + + "JOIN role ON role.domain_id=domain.domain_id WHERE role.name=?;"; + private static final String SQL_LIST_DOMAIN_AWS = "SELECT name, account FROM domain WHERE account!='';"; + private static final String SQL_GET_ROLE = "SELECT * FROM role " + + "JOIN domain ON domain.domain_id=role.domain_id " + + "WHERE domain.name=? AND role.name=?;"; + private static final String SQL_GET_ROLE_ID = "SELECT role_id FROM role WHERE domain_id=? AND name=?;"; + private static final String SQL_INSERT_ROLE = "INSERT INTO role (name, domain_id, trust) VALUES (?,?,?);"; + private static final String SQL_UPDATE_ROLE = "UPDATE role SET trust=? WHERE role_id=?;"; + private static final String SQL_DELETE_ROLE = "DELETE FROM role WHERE domain_id=? AND name=?;"; + private static final String SQL_UPDATE_ROLE_MOD_TIMESTAMP = "UPDATE role " + + "SET modified=CURRENT_TIMESTAMP(3) WHERE role_id=?;"; + private static final String SQL_LIST_ROLE = "SELECT name FROM role WHERE domain_id=?;"; + private static final String SQL_GET_ROLE_MEMBER = "SELECT principal.principal_id FROM principal " + + "JOIN role_member ON role_member.principal_id=principal.principal_id " + + "JOIN role ON role.role_id=role_member.role_id " + + "WHERE role.role_id=? AND principal.name=?;"; + private static final String SQL_LIST_ROLE_MEMBERS = "SELECT principal.name FROM principal " + + "JOIN role_member ON role_member.principal_id=principal.principal_id " + + "JOIN role ON role.role_id=role_member.role_id " + + "WHERE role.role_id=?;"; + private static final String SQL_GET_PRINCIPAL_ID = "SELECT principal_id FROM principal WHERE name=?;"; + private static final String SQL_INSERT_PRINCIPAL = "INSERT INTO principal (name) VALUES (?);"; + private static final String SQL_LAST_INSERT_ID = "SELECT LAST_INSERT_ID();"; + private static final String SQL_INSERT_ROLE_MEMBER = "INSERT INTO role_member (role_id, principal_id) VALUES (?,?);"; + private static final String SQL_DELETE_ROLE_MEMBER = "DELETE FROM role_member WHERE role_id=? AND principal_id=?;"; + private static final String SQL_INSERT_ROLE_AUDIT_LOG = "INSERT INTO role_audit_log " + + "(role_id, admin, member, action, audit_ref) VALUES (?,?,?,?,?);"; + private static final String SQL_LIST_ROLE_AUDIT_LOGS = "SELECT * FROM role_audit_log WHERE role_id=?;"; + private static final String SQL_GET_POLICY = "SELECT * FROM policy " + + "JOIN domain ON domain.domain_id=policy.domain_id WHERE domain.name=? AND policy.name=?;"; + private static final String SQL_INSERT_POLICY = "INSERT INTO policy (name, domain_id) VALUES (?,?);"; + private static final String SQL_UPDATE_POLICY = "UPDATE policy SET name=? WHERE policy_id=?;"; + private static final String SQL_UPDATE_POLICY_MOD_TIMESTAMP = "UPDATE policy " + + "SET modified=CURRENT_TIMESTAMP(3) WHERE policy_id=?;"; + private static final String SQL_GET_POLICY_ID = "SELECT policy_id FROM policy WHERE domain_id=? AND name=?;"; + private static final String SQL_DELETE_POLICY = "DELETE FROM policy WHERE domain_id=? AND name=?;"; + private static final String SQL_LIST_POLICY = "SELECT name FROM policy WHERE domain_id=?"; + private static final String SQL_LIST_ASSERTION = "SELECT * FROM assertion WHERE policy_id=?"; + private static final String SQL_GET_ASSERTION = "SELECT * FROM assertion " + + "JOIN policy ON assertion.policy_id=policy.policy_id " + + "JOIN domain ON policy.domain_id=domain.domain_id " + + "WHERE assertion.assertion_id=? AND domain.name=? AND policy.name=?;"; + private static final String SQL_CHECK_ASSERTION = "SELECT assertion_id FROM assertion " + + "WHERE policy_id=? AND role=? AND resource=? AND action=? AND effect=?;"; + private static final String SQL_INSERT_ASSERTION = "INSERT INTO assertion " + + "(policy_id, role, resource, action, effect) VALUES (?,?,?,?,?);"; + private static final String SQL_DELETE_ASSERTION = "DELETE FROM assertion " + + "WHERE policy_id=? AND assertion_id=?;"; + private static final String SQL_GET_SERVICE = "SELECT * FROM service " + + "JOIN domain ON domain.domain_id=service.domain_id WHERE domain.name=? AND service.name=?;"; + private static final String SQL_INSERT_SERVICE = "INSERT INTO service " + + "(name, provider_endpoint, executable, svc_user, svc_group, domain_id) VALUES (?,?,?,?,?,?);"; + private static final String SQL_UPDATE_SERVICE = "UPDATE service SET " + + "provider_endpoint=?, executable=?, svc_user=?, svc_group=? WHERE service_id=?;"; + private static final String SQL_DELETE_SERVICE = "DELETE FROM service WHERE domain_id=? AND name=?;"; + private static final String SQL_GET_SERVICE_ID = "SELECT service_id FROM service WHERE domain_id=? AND name=?;"; + private static final String SQL_LIST_SERVICE = "SELECT name FROM service WHERE domain_id=?;"; + private static final String SQL_LIST_PUBLIC_KEY = "SELECT * FROM public_key WHERE service_id=?;"; + private static final String SQL_GET_PUBLIC_KEY = "SELECT key_value FROM public_key WHERE service_id=? AND key_id=?;"; + private static final String SQL_INSERT_PUBLIC_KEY = "INSERT INTO public_key " + + "(service_id, key_id, key_value) VALUES (?,?,?);"; + private static final String SQL_UPDATE_PUBLIC_KEY = "UPDATE public_key SET key_value=? WHERE service_id=? AND key_id=?;"; + private static final String SQL_DELETE_PUBLIC_KEY = "DELETE FROM public_key WHERE service_id=? AND key_id=?;"; + private static final String SQL_LIST_SERVICE_HOST = "SELECT host.name FROM host " + + "JOIN service_host ON service_host.host_id=host.host_id " + + "WHERE service_host.service_id=?;"; + private static final String SQL_INSERT_SERVICE_HOST = "INSERT INTO service_host (service_id, host_id) VALUES (?,?);"; + private static final String SQL_DELETE_SERVICE_HOST = "DELETE FROM service_host WHERE service_id=? AND host_id=?;"; + private static final String SQL_GET_HOST_ID = "SELECT host_id FROM host WHERE name=?;"; + private static final String SQL_INSERT_HOST = "INSERT INTO host (name) VALUES (?);"; + private static final String SQL_INSERT_ENTITY = "INSERT INTO entity (domain_id, name, value) VALUES (?,?,?);"; + private static final String SQL_UPDATE_ENTITY = "UPDATE entity SET value=? WHERE domain_id=? AND name=?;"; + private static final String SQL_DELETE_ENTITY = "DELETE FROM entity WHERE domain_id=? AND name=?;"; + private static final String SQL_GET_ENTITY = "SELECT value FROM entity WHERE domain_id=? AND name=?;"; + private static final String SQL_LIST_ENTITY = "SELECT name FROM entity WHERE domain_id=?;"; + private static final String SQL_INSERT_DOMAIN_TEMPLATE = "INSERT INTO domain_template (domain_id, template) VALUES (?,?);"; + private static final String SQL_DELETE_DOMAIN_TEMPLATE = "DELETE FROM domain_template WHERE domain_id=? AND template=?;"; + private static final String SQL_LIST_DOMAIN_TEMPLATE = "SELECT template FROM domain_template " + + "JOIN domain ON domain_template.domain_id=domain.domain_id " + + "WHERE domain.name=?;"; + private static final String SQL_GET_DOMAIN_ROLES = "SELECT * FROM role WHERE domain_id=?;"; + private static final String SQL_GET_DOMAIN_ROLE_MEMBERS = "SELECT role.name, principal.name FROM principal " + + "JOIN role_member ON role_member.principal_id=principal.principal_id " + + "JOIN role ON role.role_id=role_member.role_id " + + "WHERE role.domain_id=?;"; + private static final String SQL_GET_DOMAIN_POLICIES = "SELECT * FROM policy WHERE domain_id=?;"; + private static final String SQL_GET_DOMAIN_POLICY_ASSERTIONS = "SELECT policy.name, " + + "assertion.effect, assertion.action, assertion.role, assertion.resource, " + + "assertion.assertion_id FROM assertion " + + "JOIN policy ON policy.policy_id=assertion.policy_id " + + "WHERE policy.domain_id=?;"; + private static final String SQL_GET_DOMAIN_SERVICES = "SELECT * FROM service WHERE domain_id=?;"; + private static final String SQL_GET_DOMAIN_SERVICES_HOSTS = "SELECT service.name, host.name FROM host " + + "JOIN service_host ON host.host_id=service_host.host_id " + + "JOIN service ON service.service_id=service_host.service_id " + + "WHERE service.domain_id=?;"; + private static final String SQL_GET_DOMAIN_SERVICES_PUBLIC_KEYS = "SELECT service.name, " + + "public_key.key_id, public_key.key_value FROM public_key " + + "JOIN service ON service.service_id=public_key.service_id " + + "WHERE service.domain_id=?;"; + private static final String SQL_LIST_POLICY_REFERENCING_ROLE = "SELECT name FROM policy " + + "JOIN assertion ON policy.policy_id=assertion.policy_id " + + "WHERE policy.domain_id=? AND assertion.role=?;"; + private static final String SQL_LIST_ROLE_ASSERTIONS = "SELECT assertion.role, assertion.resource, " + + "assertion.action, assertion.effect, assertion.assertion_id, policy.domain_id, domain.name FROM assertion " + + "JOIN policy ON assertion.policy_id=policy.policy_id " + + "JOIN domain ON policy.domain_id=domain.domain_id"; + private static final String SQL_LIST_ROLE_ASSERTION_QUERY_ACTION = " WHERE assertion.action=?;"; + private static final String SQL_LIST_ROLE_ASSERTION_NO_ACTION = " WHERE assertion.action!='assume_role';"; + private static final String SQL_LIST_ROLE_PRINCIPALS = "SELECT principal.name, role.domain_id, " + + "role.name AS role_name FROM principal " + + "JOIN role_member ON principal.principal_id=role_member.principal_id " + + "JOIN role ON role_member.role_id=role.role_id"; + private static final String SQL_LIST_ROLE_PRINCIPALS_YBY_ONLY = " WHERE principal.name LIKE 'yby.%';"; + private static final String SQL_LIST_ROLE_PRINCIPALS_QUERY = " WHERE principal.name=?;"; + private static final String SQL_LIST_TRUSTED_ROLES = "SELECT role.domain_id, role.name, " + + "policy.domain_id AS assert_domain_id, assertion.role FROM role " + + "JOIN domain ON domain.domain_id=role.domain_id " + + "JOIN assertion ON (assertion.resource=CONCAT(domain.name, \":role.\", role.name) OR assertion.resource=CONCAT(\"*:role.\", role.name)) " + + "JOIN policy ON policy.policy_id=assertion.policy_id " + + "WHERE assertion.action='assume_role';"; + + private static final String CACHE_DOMAIN = "d:"; + private static final String CACHE_ROLE = "r:"; + private static final String CACHE_POLICY = "p:"; + private static final String CACHE_SERVICE = "s:"; + private static final String CACHE_PRINCIPAL = "u:"; + private static final String CACHE_HOST = "h:"; + + Connection con = null; + boolean transactionCompleted = true; + Map objectMap = null; + + public JDBCConnection(Connection con, boolean autoCommit) throws SQLException { + this.con = con; + con.setAutoCommit(autoCommit); + transactionCompleted = autoCommit; + objectMap = new HashMap<>(); + } + + @Override + public void close() { + + if (con == null) { + return; + } + + // the client is always responsible for properly committing + // all changes before closing the connection, but in case + // we missed it, we're going to be safe and commit all + // changes before closing the connection + + try { + commitChanges(); + } catch (Exception ex) { + // error is already logged but we have to continue + // processing so we can close our connection + } + + try { + con.close(); + con = null; + } catch (SQLException ex) { + LOG.error("close: state - " + ex.getSQLState() + + ", code - " + ex.getErrorCode() + ", message - " + ex.getMessage()); + } + } + + @Override + public void rollbackChanges() { + + if (LOG.isDebugEnabled()) { + LOG.debug("rollback transaction changes..."); + } + + if (transactionCompleted) { + return; + } + + try { + con.rollback(); + } catch (SQLException ex) { + LOG.error("rollbackChanges: state - " + ex.getSQLState() + + ", code - " + ex.getErrorCode() + ", message - " + ex.getMessage()); + } + transactionCompleted = true; + try { + con.setAutoCommit(true); + } catch (SQLException ex) { + LOG.error("rollback auto-commit after failure: state - " + ex.getSQLState() + + ", code - " + ex.getErrorCode() + ", message - " + ex.getMessage()); + } + } + + @Override + public void commitChanges() { + + final String caller = "commitChanges"; + if (transactionCompleted) { + return; + } + + try { + con.commit(); + transactionCompleted = true; + con.setAutoCommit(true); + } catch (SQLException ex) { + LOG.error("commitChanges: state - " + ex.getSQLState() + + ", code - " + ex.getErrorCode() + ", message - " + ex.getMessage()); + transactionCompleted = true; + throw sqlError(ex, caller); + } + } + + Domain saveDomainSettings(String domainName, ResultSet rs, String caller) { + try { + Domain domain = new Domain().setName(domainName) + .setAuditEnabled(rs.getBoolean(ZMSConsts.DB_COLUMN_AUDIT_ENABLED)) + .setEnabled(rs.getBoolean(ZMSConsts.DB_COLUMN_ENABLED)) + .setModified(Timestamp.fromMillis(rs.getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED).getTime())) + .setDescription(saveValue(rs.getString(ZMSConsts.DB_COLUMN_DESCRIPTION))) + .setOrg(saveValue(rs.getString(ZMSConsts.DB_COLUMN_ORG))) + .setId(saveUuidValue(rs.getString(ZMSConsts.DB_COLUMN_UUID))) + .setAccount(saveValue(rs.getString(ZMSConsts.DB_COLUMN_ACCOUNT))) + .setYpmId(rs.getInt(ZMSConsts.DB_COLUMN_PRODUCT_ID)); + return domain; + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + } + + @Override + public Domain getDomain(String domainName) { + + final String caller = "getDomain"; + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN)) { + ps.setString(1, domainName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return saveDomainSettings(domainName, rs, caller); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return null; + } + + @Override + public boolean insertDomain(Domain domain) { + + int affectedRows = 0; + final String caller = "insertDomain"; + + // we need to verify that our account and product ids are unique + // in the store. we can't rely on db uniqueness check since + // some of the domains will not have these attributes set + + verifyDomainAccountUniqueness(domain.getName(), domain.getAccount(), caller); + verifyDomainProductIdUniqueness(domain.getName(), domain.getYpmId(), caller); + + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_DOMAIN)) { + ps.setString(1, domain.getName()); + ps.setString(2, processInsertValue(domain.getDescription())); + ps.setString(3, processInsertValue(domain.getOrg())); + ps.setString(4, processInsertUuidValue(domain.getId())); + ps.setBoolean(5, processInsertValue(domain.getEnabled(), true)); + ps.setBoolean(6, processInsertValue(domain.getAuditEnabled(), false)); + ps.setString(7, processInsertValue(domain.getAccount())); + ps.setInt(8, processInsertValue(domain.getYpmId())); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + void verifyDomainProductIdUniqueness(String name, Integer productId, String caller) { + + if (productId == null || productId.intValue() == 0) { + return; + } + + String domainName = lookupDomainById(null, productId); + if (domainName != null && !domainName.equals(name)) { + throw requestError(caller, "Product Id: " + productId + + " is already assigned to domain: " + domainName); + } + } + + void verifyDomainAccountUniqueness(String name, String account, String caller) { + + if (account == null || account.isEmpty()) { + return; + } + + String domainName = lookupDomainById(account, 0); + if (domainName != null && !domainName.equals(name)) { + throw requestError(caller, "Account Id: " + account + + " is already assigned to domain: " + domainName); + } + } + + @Override + public boolean updateDomain(Domain domain) { + + int affectedRows = 0; + final String caller = "updateDomain"; + + // we need to verify that our account and product ids are unique + // in the store. we can't rely on db uniqueness check since + // some of the domains will not have these attributes set + + verifyDomainAccountUniqueness(domain.getName(), domain.getAccount(), caller); + verifyDomainProductIdUniqueness(domain.getName(), domain.getYpmId(), caller); + + try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_DOMAIN)) { + ps.setString(1, processInsertValue(domain.getDescription())); + ps.setString(2, processInsertValue(domain.getOrg())); + ps.setString(3, processInsertUuidValue(domain.getId())); + ps.setBoolean(4, processInsertValue(domain.getEnabled(), true)); + ps.setBoolean(5, processInsertValue(domain.getAuditEnabled(), false)); + ps.setString(6, processInsertValue(domain.getAccount())); + ps.setInt(7, processInsertValue(domain.getYpmId())); + ps.setString(8, domain.getName()); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean updateDomainModTimestamp(String domainName) { + + int affectedRows = 0; + final String caller = "updateDomainModTimestamp"; + + try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_DOMAIN_MOD_TIMESTAMP)) { + ps.setString(1, domainName); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public long getDomainModTimestamp(String domainName) { + + long modTime = 0; + final String caller = "getDomainModTimestamp"; + + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_MOD_TIMESTAMP)) { + ps.setString(1, domainName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + modTime = rs.getTimestamp(1).getTime(); + } + } + } catch (SQLException ex) { + // ignore any failures and return default value 0 + } + return modTime; + } + + @Override + public boolean deleteDomain(String domainName) { + + int affectedRows = 0; + final String caller = "deleteDomain"; + + try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_DOMAIN)) { + ps.setString(1, domainName); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + PreparedStatement prepareScanStatement(Connection con, String prefix, long modifiedSince) + throws SQLException { + + PreparedStatement ps = null; + if (prefix != null && prefix.length() > 0) { + int len = prefix.length(); + char c = (char) (prefix.charAt(len - 1) + 1); + String stop = prefix.substring(0, len - 1) + c; + if (modifiedSince != 0) { + ps = con.prepareStatement(SQL_LIST_DOMAIN_PREFIX_MODIFIED); + ps.setString(1, prefix); + ps.setString(2, stop); + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + ps.setTimestamp(3, new java.sql.Timestamp(modifiedSince), cal); + } else { + ps = con.prepareStatement(SQL_LIST_DOMAIN_PREFIX); + ps.setString(1, prefix); + ps.setString(2, stop); + } + } else if (modifiedSince != 0) { + ps = con.prepareStatement(SQL_LIST_DOMAIN_MODIFIED); + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + ps.setTimestamp(1, new java.sql.Timestamp(modifiedSince), cal); + } else { + ps = con.prepareStatement(SQL_LIST_DOMAIN); + } + return ps; + } + + PreparedStatement prepareScanByRoleStatement(Connection con, String roleMember, String roleName) + throws SQLException { + + PreparedStatement ps = null; + boolean memberPresent = (roleMember != null && !roleMember.isEmpty()); + boolean rolePresent = (roleName != null && !roleName.isEmpty()); + if (memberPresent && rolePresent) { + ps = con.prepareStatement(SQL_LIST_DOMAIN_ROLE_NAME_MEMBER); + ps.setString(1, roleMember); + ps.setString(2, roleName); + } else if (memberPresent) { + ps = con.prepareStatement(SQL_LIST_DOMAIN_ROLE_MEMBER); + ps.setString(1, roleMember); + } else if (rolePresent) { + ps = con.prepareStatement(SQL_LIST_DOMAIN_ROLE_NAME); + ps.setString(1, roleName); + } else { + ps = con.prepareStatement(SQL_LIST_DOMAIN); + } + return ps; + } + + @Override + public List lookupDomainByRole(String roleMember, String roleName) { + + final String caller = "lookupDomainByRole"; + + // it's possible that we'll get duplicate domain names returned + // from this result - e.g. when no role name is filtered on so + // we're going to automatically skip those by using a set + + Set uniqueDomains = new HashSet<>(); + try (PreparedStatement ps = prepareScanByRoleStatement(con, roleMember, roleName)) { + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + uniqueDomains.add(rs.getString(ZMSConsts.DB_COLUMN_NAME)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + List domains = new ArrayList<>(uniqueDomains); + Collections.sort(domains); + return domains; + } + + @Override + public String lookupDomainById(String account, int productId) { + + final String caller = "lookupDomain"; + + String sqlCmd = null; + if (account != null) { + sqlCmd = SQL_GET_DOMAIN_WITH_ACCOUNT; + } else { + sqlCmd = SQL_GET_DOMAIN_WITH_PRODUCT_ID; + } + + String domainName = null; + try (PreparedStatement ps = con.prepareStatement(sqlCmd)) { + + if (account != null) { + ps.setString(1, account.trim()); + } else { + ps.setInt(1, productId); + } + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + domainName = rs.getString(1); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + return domainName; + } + + @Override + public List listDomains(String prefix, long modifiedSince) { + + final String caller = "listDomains"; + + List domains = new ArrayList<>(); + try (PreparedStatement ps = prepareScanStatement(con, prefix, modifiedSince)) { + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + domains.add(rs.getString(ZMSConsts.DB_COLUMN_NAME)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + Collections.sort(domains); + return domains; + } + + @Override + public boolean insertDomainTemplate(String domainName, String templateName, String params) { + + final String caller = "insertDomainTemplate"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_DOMAIN_TEMPLATE)) { + ps.setInt(1, domainId); + ps.setString(2, templateName); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean deleteDomainTemplate(String domainName, String templateName, String params) { + + final String caller = "deleteDomainTemplate"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_DOMAIN_TEMPLATE)) { + ps.setInt(1, domainId); + ps.setString(2, templateName); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public List listDomainTemplates(String domainName) { + + final String caller = "listDomainTemplates"; + + List templates = new ArrayList<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_LIST_DOMAIN_TEMPLATE)) { + ps.setString(1, domainName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + templates.add(rs.getString(1)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + Collections.sort(templates); + return templates; + } + + int getDomainId(Connection con, String domainName) { + + final String caller = "getDomainId"; + + // first check to see if our cache contains this value + // otherwise we'll contact the MySQL Server + + StringBuilder cacheKeyBldr = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + cacheKeyBldr.append(CACHE_DOMAIN).append(domainName); + String cacheKey = cacheKeyBldr.toString(); + + Integer value = objectMap.get(cacheKey); + if (value != null) { + return value; + } + + int domainId = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_ID)) { + ps.setString(1, domainName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + domainId = rs.getInt(1); + } + } + } catch (SQLException ex) { + LOG.error("unable to get domain id for name: " + domainName + + " error code: " + ex.getErrorCode() + " msg: " + ex.getMessage()); + } + + // before returning the value update our cache + + if (domainId != 0) { + objectMap.put(cacheKey, domainId); + } + + return domainId; + } + + int getPolicyId(Connection con, int domainId, String policyName) { + + final String caller = "getPolicyId"; + + // first check to see if our cache contains this value + // otherwise we'll contact the MySQL Server + + StringBuilder cacheKeyBldr = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + cacheKeyBldr.append(CACHE_POLICY).append(domainId).append('.').append(policyName); + String cacheKey = cacheKeyBldr.toString(); + + Integer value = objectMap.get(cacheKey); + if (value != null) { + return value; + } + + int policyId = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_GET_POLICY_ID)) { + ps.setInt(1, domainId); + ps.setString(2, policyName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + policyId = rs.getInt(1); + } + } + } catch (SQLException ex) { + LOG.error("unable to get polcy id for name: " + policyName + + " error code: " + ex.getErrorCode() + " msg: " + ex.getMessage()); + } + + // before returning the value update our cache + + if (policyId != 0) { + objectMap.put(cacheKey, policyId); + } + + return policyId; + } + + int getRoleId(Connection con, int domainId, String roleName) { + + final String caller = "getRoleId"; + + // first check to see if our cache contains this value + // otherwise we'll contact the MySQL Server + + StringBuilder cacheKeyBldr = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + cacheKeyBldr.append(CACHE_ROLE).append(domainId).append('.').append(roleName); + String cacheKey = cacheKeyBldr.toString(); + + Integer value = objectMap.get(cacheKey); + if (value != null) { + return value; + } + + int roleId = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_GET_ROLE_ID)) { + ps.setInt(1, domainId); + ps.setString(2, roleName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + roleId = rs.getInt(1); + } + } + } catch (SQLException ex) { + LOG.error("unable to get role id for name: " + roleName + + " error code: " + ex.getErrorCode() + " msg: " + ex.getMessage()); + } + + // before returning the value update our cache + + if (roleId != 0) { + objectMap.put(cacheKey, roleId); + } + + return roleId; + } + + int getServiceId(Connection con, int domainId, String serviceName) { + + final String caller = "getServiceId"; + + // first check to see if our cache contains this value + // otherwise we'll contact the MySQL Server + + StringBuilder cacheKeyBldr = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + cacheKeyBldr.append(CACHE_SERVICE).append(domainId).append('.').append(serviceName); + String cacheKey = cacheKeyBldr.toString(); + + Integer value = objectMap.get(cacheKey); + if (value != null) { + return value; + } + + int serviceId = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_GET_SERVICE_ID)) { + ps.setInt(1, domainId); + ps.setString(2, serviceName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + serviceId = rs.getInt(1); + } + } + } catch (SQLException ex) { + LOG.error("unable to get service id for name: " + serviceName + + " error code: " + ex.getErrorCode() + " msg: " + ex.getMessage()); + } + + // before returning the value update our cache + + if (serviceId != 0) { + objectMap.put(cacheKey, serviceId); + } + + return serviceId; + } + + int getPrincipalId(Connection con, String principal) { + + final String caller = "getPrincipalId"; + + // first check to see if our cache contains this value + // otherwise we'll contact the MySQL Server + + StringBuilder cacheKeyBldr = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + cacheKeyBldr.append(CACHE_PRINCIPAL).append(principal); + String cacheKey = cacheKeyBldr.toString(); + + Integer value = objectMap.get(cacheKey); + if (value != null) { + return value; + } + + int principalId = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_GET_PRINCIPAL_ID)) { + ps.setString(1, principal); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + principalId = rs.getInt(1); + } + } + } catch (SQLException ex) { + LOG.error("unable to get principal id for name: " + principal + + " error code: " + ex.getErrorCode() + + " msg: " + ex.getMessage()); + } + + // before returning the value update our cache + + if (principalId != 0) { + objectMap.put(cacheKey, principalId); + } + + return principalId; + } + + int getHostId(Connection con, String hostName) { + + final String caller = "getHostId"; + + // first check to see if our cache contains this value + // otherwise we'll contact the MySQL Server + + StringBuilder cacheKeyBldr = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT); + cacheKeyBldr.append(CACHE_HOST).append(hostName); + String cacheKey = cacheKeyBldr.toString(); + + Integer value = objectMap.get(cacheKey); + if (value != null) { + return value; + } + + int hostId = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_GET_HOST_ID)) { + ps.setString(1, hostName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + hostId = rs.getInt(1); + } + } + } catch (SQLException ex) { + LOG.error("unable to get host id for name: " + hostName + + " error code: " + ex.getErrorCode() + " msg: " + ex.getMessage()); + } + + // before returning the value update our cache + + if (hostId != 0) { + objectMap.put(cacheKey, hostId); + } + + return hostId; + } + + int getLastInsertId(Connection con) { + + int lastInsertId = 0; + final String caller = "getLastInsertId"; + + try (PreparedStatement ps = con.prepareStatement(SQL_LAST_INSERT_ID)) { + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + lastInsertId = rs.getInt(1); + } + } + } catch (SQLException ex) { + LOG.error("unable to get last insert id - error code: " + ex.getErrorCode() + + " msg: " + ex.getMessage()); + } + return lastInsertId; + } + + @Override + public Role getRole(String domainName, String roleName) { + + final String caller = "getRole"; + + try (PreparedStatement ps = con.prepareStatement(SQL_GET_ROLE)) { + ps.setString(1, domainName); + ps.setString(2, roleName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + Role role = new Role().setName(ZMSUtils.roleResourceName(domainName, roleName)) + .setModified(Timestamp.fromMillis(rs.getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED).getTime())) + .setTrust(saveValue(rs.getString(ZMSConsts.DB_COLUMN_TRUST))); + return role; + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return null; + } + + String extractObjectName(String domainName, String fullName, String objType) { + + // generate prefix to compare with + + StringBuilder prefixBuffer = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT) + .append(domainName).append(objType); + String prefix = prefixBuffer.toString(); + if (!fullName.startsWith(prefix)) { + return null; + } + return fullName.substring(prefix.length()); + } + + String extractRoleName(String domainName, String fullRoleName) { + return extractObjectName(domainName, fullRoleName, ":role."); + } + + String extractPolicyName(String domainName, String fullPolicyName) { + return extractObjectName(domainName, fullPolicyName, ":policy."); + } + + String extractServiceName(String domainName, String fullServiceName) { + return extractObjectName(domainName, fullServiceName, "."); + } + + @Override + public boolean insertRole(String domainName, Role role) { + + int affectedRows = 0; + final String caller = "insertRole"; + + String roleName = extractRoleName(domainName, role.getName()); + if (roleName == null) { + throw requestError(caller, "domain name mismatch: " + domainName + + " insert role name: " + role.getName()); + } + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ROLE)) { + ps.setString(1, roleName); + ps.setInt(2, domainId); + ps.setString(3, processInsertValue(role.getTrust())); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean updateRole(String domainName, Role role) { + + int affectedRows = 0; + final String caller = "updateRole"; + + String roleName = extractRoleName(domainName, role.getName()); + if (roleName == null) { + throw requestError(caller, "domain name mismatch: " + domainName + + " update role name: " + role.getName()); + } + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int roleId = getRoleId(con, domainId, roleName); + if (roleId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_ROLE, ZMSUtils.roleResourceName(domainName, roleName)); + } + + try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_ROLE)) { + ps.setString(1, processInsertValue(role.getTrust())); + ps.setInt(2, roleId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + return (affectedRows > 0); + } + + @Override + public boolean updateRoleModTimestamp(String domainName, String roleName) { + + int affectedRows = 0; + final String caller = "updateRoleModTimestamp"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int roleId = getRoleId(con, domainId, roleName); + if (roleId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_ROLE, ZMSUtils.roleResourceName(domainName, roleName)); + } + + try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_ROLE_MOD_TIMESTAMP)) { + ps.setInt(1, roleId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean deleteRole(String domainName, String roleName) { + + final String caller = "deleteRole"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_ROLE)) { + ps.setInt(1, domainId); + ps.setString(2, roleName); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public List listRoles(String domainName) { + + final String caller = "listRoles"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + List roles = new ArrayList<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ROLE)) { + ps.setInt(1, domainId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + roles.add(rs.getString(1)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + Collections.sort(roles); + return roles; + } + + @Override + public List listRoleMembers(String domainName, String roleName) { + + final String caller = "listRoleMembers"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int roleId = getRoleId(con, domainId, roleName); + if (roleId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_ROLE, ZMSUtils.roleResourceName(domainName, roleName)); + } + List members = new ArrayList<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ROLE_MEMBERS)) { + ps.setInt(1, roleId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + members.add(rs.getString(1)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + Collections.sort(members); + return members; + } + + @Override + public List listRoleAuditLogs(String domainName, String roleName) { + + final String caller = "listRoleAuditLogs"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int roleId = getRoleId(con, domainId, roleName); + if (roleId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_ROLE, ZMSUtils.roleResourceName(domainName, roleName)); + } + List logs = new ArrayList<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ROLE_AUDIT_LOGS)) { + ps.setInt(1, roleId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + RoleAuditLog log = new RoleAuditLog(); + log.setAction(rs.getString(ZMSConsts.DB_COLUMN_ACTION)); + log.setMember(rs.getString(ZMSConsts.DB_COLUMN_MEMBER)); + log.setAdmin(rs.getString(ZMSConsts.DB_COLUMN_ADMIN)); + log.setAuditRef(saveValue(rs.getString(ZMSConsts.DB_COLUMN_AUDIT_REF))); + log.setCreated(Timestamp.fromMillis(rs.getTimestamp(ZMSConsts.DB_COLUMN_CREATED).getTime())); + logs.add(log); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return logs; + } + + boolean parsePrincipal(String principal, StringBuilder domain, StringBuilder name) { + int idx = principal.lastIndexOf('.'); + if (idx == -1 || idx == 0 || idx == principal.length() - 1) { + return false; + } + domain.append(principal.substring(0, idx)); + name.append(principal.substring(idx + 1)); + return true; + } + + @Override + public Membership getRoleMember(String domainName, String roleName, String member) { + + final String caller = "getRoleMember"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int roleId = getRoleId(con, domainId, roleName); + if (roleId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_ROLE, ZMSUtils.roleResourceName(domainName, roleName)); + } + + Membership membership = new Membership() + .setMemberName(member) + .setRoleName(ZMSUtils.roleResourceName(domainName, roleName)) + .setIsMember(false); + + try (PreparedStatement ps = con.prepareStatement(SQL_GET_ROLE_MEMBER)) { + ps.setInt(1, roleId); + ps.setString(2, member); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + membership.setIsMember(true); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return membership; + } + + int insertPrincipal(Connection con, String principal) { + + int affectedRows = 0; + final String caller = "insertPrincipal"; + + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_PRINCIPAL)) { + ps.setString(1, principal); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + int principalId = 0; + if (affectedRows == 1) { + principalId = getLastInsertId(con); + } + return principalId; + } + + int insertHost(Connection con, String hostName) { + + int affectedRows = 0; + final String caller = "insertHost"; + + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_HOST)) { + ps.setString(1, hostName); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + int hostId = 0; + if (affectedRows == 1) { + hostId = getLastInsertId(con); + } + return hostId; + } + + @Override + public boolean insertRoleMember(String domainName, String roleName, String principal, + String admin, String auditRef) { + + final String caller = "insertRoleMember"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int roleId = getRoleId(con, domainId, roleName); + if (roleId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_ROLE, ZMSUtils.roleResourceName(domainName, roleName)); + } + if (!validatePrincipalDomain(principal)) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, principal); + } + int principalId = getPrincipalId(con, principal); + if (principalId == 0) { + principalId = insertPrincipal(con, principal); + if (principalId == 0) { + throw internalServerError(caller, "Unable to insert principal: " + principal); + } + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ROLE_MEMBER)) { + ps.setInt(1, roleId); + ps.setInt(2, principalId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + boolean result = (affectedRows > 0); + + // add audit log entry for this change if the add was successful + // add return the result of the audit log insert operation + + if (result) { + result = insertRoleAuditLog(con, roleId, admin, principal, "ADD", auditRef); + } + + return result; + } + + @Override + public boolean deleteRoleMember(String domainName, String roleName, String principal, + String admin, String auditRef) { + + final String caller = "deleteRoleMember"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int roleId = getRoleId(con, domainId, roleName); + if (roleId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_ROLE, ZMSUtils.roleResourceName(domainName, roleName)); + } + int principalId = getPrincipalId(con, principal); + if (principalId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_PRINCIPAL, principal); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_ROLE_MEMBER)) { + ps.setInt(1, roleId); + ps.setInt(2, principalId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + boolean result = (affectedRows > 0); + + // add audit log entry for this change if the delete was successful + // add return the result of the audit log insert operation + + if (result) { + result = insertRoleAuditLog(con, roleId, admin, principal, "DELETE", auditRef); + } + + return result; + } + + boolean insertRoleAuditLog(Connection con, int roleId, String admin, String member, + String action, String auditRef) { + + int affectedRows = 0; + final String caller = "insertRoleAuditEntry"; + + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ROLE_AUDIT_LOG)) { + ps.setInt(1, roleId); + ps.setString(2, processInsertValue(admin)); + ps.setString(3, member); + ps.setString(4, action); + ps.setString(5, processInsertValue(auditRef)); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public Assertion getAssertion(String domainName, String policyName, Long assertionId) { + + final String caller = "getAssertion"; + + Assertion assertion = null; + try (PreparedStatement ps = con.prepareStatement(SQL_GET_ASSERTION)) { + ps.setInt(1, assertionId.intValue()); + ps.setString(2, domainName); + ps.setString(3, policyName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + assertion = new Assertion(); + assertion.setRole(ZMSUtils.roleResourceName(domainName, rs.getString(ZMSConsts.DB_COLUMN_ROLE))); + assertion.setResource(rs.getString(ZMSConsts.DB_COLUMN_RESOURCE)); + assertion.setAction(rs.getString(ZMSConsts.DB_COLUMN_ACTION)); + assertion.setEffect(AssertionEffect.valueOf(rs.getString(ZMSConsts.DB_COLUMN_EFFECT))); + assertion.setId((long) rs.getInt(ZMSConsts.DB_COLUMN_ASSERT_ID)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return assertion; + } + + @Override + public Policy getPolicy(String domainName, String policyName) { + + final String caller = "getPolicy"; + + try (PreparedStatement ps = con.prepareStatement(SQL_GET_POLICY)) { + ps.setString(1, domainName); + ps.setString(2, policyName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + Policy policy = new Policy().setName(ZMSUtils.policyResourceName(domainName, policyName)) + .setModified(Timestamp.fromMillis(rs.getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED).getTime())); + return policy; + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return null; + } + + @Override + public boolean insertPolicy(String domainName, Policy policy) { + + int affectedRows = 0; + final String caller = "insertPolicy"; + + String policyName = extractPolicyName(domainName, policy.getName()); + if (policyName == null) { + throw requestError(caller, "domain name mismatch: " + domainName + + " insert policy name: " + policy.getName()); + } + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_POLICY)) { + ps.setString(1, policyName); + ps.setInt(2, domainId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean updatePolicy(String domainName, Policy policy) { + + int affectedRows = 0; + final String caller = "updatePolicy"; + + String policyName = extractPolicyName(domainName, policy.getName()); + if (policyName == null) { + throw requestError(caller, "domain name mismatch: " + domainName + + " update policy name: " + policy.getName()); + } + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int policyId = getPolicyId(con, domainId, policyName); + if (policyId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_POLICY, ZMSUtils.policyResourceName(domainName, policyName)); + } + try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_POLICY)) { + ps.setString(1, policyName); + ps.setInt(2, policyId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean updatePolicyModTimestamp(String domainName, String policyName) { + + int affectedRows = 0; + final String caller = "updatePolicyModTimestamp"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int policyId = getPolicyId(con, domainId, policyName); + if (policyId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_POLICY, ZMSUtils.policyResourceName(domainName, policyName)); + } + + try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_POLICY_MOD_TIMESTAMP)) { + ps.setInt(1, policyId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean deletePolicy(String domainName, String policyName) { + + final String caller = "deletePolicy"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_POLICY)) { + ps.setInt(1, domainId); + ps.setString(2, policyName); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public List listPolicies(String domainName, String assertionRoleName) { + + final String caller = "listPolicies"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + List policies = new ArrayList<>(); + String sqlStatement = null; + if (assertionRoleName == null) { + sqlStatement = SQL_LIST_POLICY; + } else { + sqlStatement = SQL_LIST_POLICY_REFERENCING_ROLE; + } + try (PreparedStatement ps = con.prepareStatement(sqlStatement)) { + ps.setInt(1, domainId); + if (assertionRoleName != null) { + ps.setString(2, assertionRoleName); + } + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + policies.add(rs.getString(1)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + Collections.sort(policies); + return policies; + } + + @Override + public boolean insertAssertion(String domainName, String policyName, Assertion assertion) { + + final String caller = "insertAssertion"; + + String roleName = extractRoleName(domainName, assertion.getRole()); + if (roleName == null) { + throw requestError(caller, "domain name mismatch: " + domainName + + " assertion role name: " + assertion.getRole()); + } + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int policyId = getPolicyId(con, domainId, policyName); + if (policyId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_POLICY, ZMSUtils.policyResourceName(domainName, policyName)); + } + + // special handling for assertions since we don't want to have duplicates + // and we don't want to setup a unique key across all values in the row + + try (PreparedStatement ps = con.prepareStatement(SQL_CHECK_ASSERTION)) { + ps.setInt(1, policyId); + ps.setString(2, roleName); + ps.setString(3, assertion.getResource()); + ps.setString(4, assertion.getAction()); + ps.setString(5, processInsertValue(assertion.getEffect())); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return true; + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + // at this point we know we don't have another assertion with the same + // values so we'll go ahead and add one + + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ASSERTION)) { + ps.setInt(1, policyId); + ps.setString(2, roleName); + ps.setString(3, assertion.getResource()); + ps.setString(4, assertion.getAction()); + ps.setString(5, processInsertValue(assertion.getEffect())); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + boolean result = (affectedRows > 0); + + if (result) { + assertion.setId((long) getLastInsertId(con)); + } + return result; + } + + @Override + public boolean deleteAssertion(String domainName, String policyName, Long assertionId) { + + final String caller = "deleteAssertion"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int policyId = getPolicyId(con, domainId, policyName); + if (policyId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_POLICY, ZMSUtils.policyResourceName(domainName, policyName)); + } + + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_ASSERTION)) { + ps.setInt(1, policyId); + ps.setInt(2, assertionId.intValue()); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public List listAssertions(String domainName, String policyName) { + + final String caller = "listAssertions"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int policyId = getPolicyId(con, domainId, policyName); + if (policyId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_POLICY, ZMSUtils.policyResourceName(domainName, policyName)); + } + List assertions = new ArrayList<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ASSERTION)) { + ps.setInt(1, policyId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + Assertion assertion = new Assertion(); + assertion.setRole(ZMSUtils.roleResourceName(domainName, rs.getString(ZMSConsts.DB_COLUMN_ROLE))); + assertion.setResource(rs.getString(ZMSConsts.DB_COLUMN_RESOURCE)); + assertion.setAction(rs.getString(ZMSConsts.DB_COLUMN_ACTION)); + assertion.setEffect(AssertionEffect.valueOf(rs.getString(ZMSConsts.DB_COLUMN_EFFECT))); + assertion.setId((long) rs.getInt(ZMSConsts.DB_COLUMN_ASSERT_ID)); + assertions.add(assertion); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return assertions; + } + + String saveValue(String value) { + return (value.isEmpty()) ? null : value; + } + + UUID saveUuidValue(String value) { + return (value.isEmpty()) ? null : UUID.fromString(value); + } + + @Override + public ServiceIdentity getServiceIdentity(String domainName, String serviceName) { + + final String caller = "getServiceIdentity"; + + try (PreparedStatement ps = con.prepareStatement(SQL_GET_SERVICE)) { + ps.setString(1, domainName); + ps.setString(2, serviceName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + ServiceIdentity serviceIdentity = new ServiceIdentity() + .setName(ZMSUtils.serviceResourceName(domainName, serviceName)) + .setModified(Timestamp.fromMillis(rs.getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED).getTime())) + .setProviderEndpoint(saveValue(rs.getString(ZMSConsts.DB_COLUMN_PROVIDER_ENDPOINT))) + .setExecutable(saveValue(rs.getString(ZMSConsts.DB_COLUMN_EXECTUABLE))) + .setUser(saveValue(rs.getString(ZMSConsts.DB_COLUMN_SVC_USER))) + .setGroup(saveValue(rs.getString(ZMSConsts.DB_COLUMN_SVC_GROUP))); + + return serviceIdentity; + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return null; + } + + int processInsertValue(Integer value) { + return (value == null) ? 0 : value.intValue(); + } + + String processInsertValue(String value) { + return (value == null) ? "" : value.trim(); + } + + boolean processInsertValue(Boolean value, boolean defaultValue) { + return (value == null) ? defaultValue : value; + } + + String processInsertValue(AssertionEffect value) { + return (value == null) ? ZMSConsts.ASSERTION_EFFECT_ALLOW : value.toString(); + } + + String processInsertUuidValue(UUID value) { + return (value == null) ? "" : value.toString(); + } + + @Override + public boolean insertServiceIdentity(String domainName, ServiceIdentity service) { + + int affectedRows = 0; + final String caller = "insertServiceIdentity"; + + String serviceName = extractServiceName(domainName, service.getName()); + if (serviceName == null) { + throw requestError(caller, "domain name mismatch: " + domainName + + " insert service name: " + service.getName()); + } + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_SERVICE)) { + ps.setString(1, serviceName); + ps.setString(2, processInsertValue(service.getProviderEndpoint())); + ps.setString(3, processInsertValue(service.getExecutable())); + ps.setString(4, processInsertValue(service.getUser())); + ps.setString(5, processInsertValue(service.getGroup())); + ps.setInt(6, domainId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean updateServiceIdentity(String domainName, ServiceIdentity service) { + + int affectedRows = 0; + final String caller = "updateServiceIdentity"; + + String serviceName = extractServiceName(domainName, service.getName()); + if (serviceName == null) { + throw requestError(caller, "domain name mismatch: " + domainName + + " update service name: " + service.getName()); + } + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int serviceId = getServiceId(con, domainId, serviceName); + if (serviceId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_SERVICE, ZMSUtils.serviceResourceName(domainName, serviceName)); + } + try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_SERVICE)) { + ps.setString(1, processInsertValue(service.getProviderEndpoint())); + ps.setString(2, processInsertValue(service.getExecutable())); + ps.setString(3, processInsertValue(service.getUser())); + ps.setString(4, processInsertValue(service.getGroup())); + ps.setInt(5, serviceId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean deleteServiceIdentity(String domainName, String serviceName) { + + final String caller = "deleteServiceIdentity"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_SERVICE)) { + ps.setInt(1, domainId); + ps.setString(2, serviceName); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public List listServiceIdentities(String domainName) { + + final String caller = "listServiceIdentities"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + List services = new ArrayList<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_LIST_SERVICE)) { + ps.setInt(1, domainId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + services.add(rs.getString(1)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + Collections.sort(services); + return services; + } + + @Override + public List listPublicKeys(String domainName, String serviceName) { + + final String caller = "listPublicKeys"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int serviceId = getServiceId(con, domainId, serviceName); + if (serviceId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_SERVICE, ZMSUtils.serviceResourceName(domainName, serviceName)); + } + List publicKeys = new ArrayList<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_LIST_PUBLIC_KEY)) { + ps.setInt(1, serviceId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + PublicKeyEntry publicKey = new PublicKeyEntry() + .setId(rs.getString(ZMSConsts.DB_COLUMN_KEY_ID)) + .setKey(rs.getString(ZMSConsts.DB_COLUMN_KEY_VALUE)); + publicKeys.add(publicKey); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return publicKeys; + } + + @Override + public PublicKeyEntry getPublicKeyEntry(String domainName, String serviceName, String keyId) { + + final String caller = "getPublicKeyEntry"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int serviceId = getServiceId(con, domainId, serviceName); + if (serviceId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_SERVICE, ZMSUtils.serviceResourceName(domainName, serviceName)); + } + try (PreparedStatement ps = con.prepareStatement(SQL_GET_PUBLIC_KEY)) { + ps.setInt(1, serviceId); + ps.setString(2, keyId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + PublicKeyEntry publicKey = new PublicKeyEntry().setId(keyId) + .setKey(rs.getString(ZMSConsts.DB_COLUMN_KEY_VALUE)); + return publicKey; + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return null; + } + + @Override + public boolean insertPublicKeyEntry(String domainName, String serviceName, PublicKeyEntry publicKey) { + + final String caller = "insertPublicKeyEntry"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int serviceId = getServiceId(con, domainId, serviceName); + if (serviceId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_SERVICE, ZMSUtils.serviceResourceName(domainName, serviceName)); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_PUBLIC_KEY)) { + ps.setInt(1, serviceId); + ps.setString(2, publicKey.getId()); + ps.setString(3, publicKey.getKey()); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean updatePublicKeyEntry(String domainName, String serviceName, PublicKeyEntry publicKey) { + + final String caller = "updatePublicKeyEntry"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int serviceId = getServiceId(con, domainId, serviceName); + if (serviceId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_SERVICE, ZMSUtils.serviceResourceName(domainName, serviceName)); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_PUBLIC_KEY)) { + ps.setString(1, publicKey.getKey()); + ps.setInt(2, serviceId); + ps.setString(3, publicKey.getId()); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean deletePublicKeyEntry(String domainName, String serviceName, String keyId) { + + final String caller = "deletePublicKeyEntry"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int serviceId = getServiceId(con, domainId, serviceName); + if (serviceId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_SERVICE, ZMSUtils.serviceResourceName(domainName, serviceName)); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_PUBLIC_KEY)) { + ps.setInt(1, serviceId); + ps.setString(2, keyId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public List listServiceHosts(String domainName, String serviceName) { + + final String caller = "listServiceHosts"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int serviceId = getServiceId(con, domainId, serviceName); + if (serviceId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_SERVICE, ZMSUtils.serviceResourceName(domainName, serviceName)); + } + List hosts = new ArrayList<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_LIST_SERVICE_HOST)) { + ps.setInt(1, serviceId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + hosts.add(rs.getString(ZMSConsts.DB_COLUMN_NAME)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return hosts; + } + + @Override + public boolean insertServiceHost(String domainName, String serviceName, String hostName) { + + final String caller = "insertServiceHost"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int serviceId = getServiceId(con, domainId, serviceName); + if (serviceId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_SERVICE, ZMSUtils.serviceResourceName(domainName, serviceName)); + } + int hostId = getHostId(con, hostName); + if (hostId == 0) { + hostId = insertHost(con, hostName); + if (hostId == 0) { + throw internalServerError(caller, "Unable to insert host: " + hostName); + } + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_SERVICE_HOST)) { + ps.setInt(1, serviceId); + ps.setInt(2, hostId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean deleteServiceHost(String domainName, String serviceName, String hostName) { + + final String caller = "deleteServiceHost"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int serviceId = getServiceId(con, domainId, serviceName); + if (serviceId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_SERVICE, ZMSUtils.serviceResourceName(domainName, serviceName)); + } + int hostId = getHostId(con, hostName); + if (hostId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_HOST, hostName); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_SERVICE_HOST)) { + ps.setInt(1, serviceId); + ps.setInt(2, hostId); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean insertEntity(String domainName, Entity entity) { + + final String caller = "insertEntity"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ENTITY)) { + ps.setInt(1, domainId); + ps.setString(2, entity.getName()); + ps.setString(3, JSON.string(entity.getValue())); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean updateEntity(String domainName, Entity entity) { + + final String caller = "updateEntity"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_ENTITY)) { + ps.setString(1, JSON.string(entity.getValue())); + ps.setInt(2, domainId); + ps.setString(3, entity.getName()); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public boolean deleteEntity(String domainName, String entityName) { + + final String caller = "deleteEntity"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + int affectedRows = 0; + try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_ENTITY)) { + ps.setInt(1, domainId); + ps.setString(2, entityName); + affectedRows = ps.executeUpdate(); + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return (affectedRows > 0); + } + + @Override + public Entity getEntity(String domainName, String entityName) { + + final String caller = "getEntity"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + try (PreparedStatement ps = con.prepareStatement(SQL_GET_ENTITY)) { + ps.setInt(1, domainId); + ps.setString(2, entityName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + Entity entity = new Entity().setName(entityName) + .setValue(JSON.fromString(rs.getString(ZMSConsts.DB_COLUMN_VALUE), Struct.class)); + return entity; + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + return null; + } + + @Override + public List listEntities(String domainName) { + + final String caller = "listEntities"; + + int domainId = getDomainId(con, domainName); + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + List entities = new ArrayList<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ENTITY)) { + ps.setInt(1, domainId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + entities.add(rs.getString(1)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + Collections.sort(entities); + return entities; + } + + void getAthenzDomainRoles(String domainName, int domainId, AthenzDomain athenzDomain, String caller) { + + Map roleMap = new HashMap<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_ROLES)) { + ps.setInt(1, domainId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + String roleName = rs.getString(ZMSConsts.DB_COLUMN_NAME); + Role role = new Role().setName(ZMSUtils.roleResourceName(domainName, roleName)) + .setModified(Timestamp.fromMillis(rs.getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED).getTime())) + .setTrust(saveValue(rs.getString(ZMSConsts.DB_COLUMN_TRUST))); + roleMap.put(roleName, role); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_ROLE_MEMBERS)) { + ps.setInt(1, domainId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + String roleName = rs.getString(1); + Role role = roleMap.get(roleName); + if (role == null) { + continue; + } + List members = role.getMembers(); + if (members == null) { + members = new ArrayList<>(); + role.setMembers(members); + } + members.add(rs.getString(2)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + athenzDomain.getRoles().addAll(roleMap.values()); + } + + void getAthenzDomainPolicies(String domainName, int domainId, AthenzDomain athenzDomain, String caller) { + + Map policyMap = new HashMap<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_POLICIES)) { + ps.setInt(1, domainId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + String policyName = rs.getString(ZMSConsts.DB_COLUMN_NAME); + Policy policy = new Policy().setName(ZMSUtils.policyResourceName(domainName, policyName)) + .setModified(Timestamp.fromMillis(rs.getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED).getTime())); + policyMap.put(policyName, policy); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_POLICY_ASSERTIONS)) { + ps.setInt(1, domainId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + String policyName = rs.getString(1); + Policy policy = policyMap.get(policyName); + if (policy == null) { + continue; + } + List assertions = policy.getAssertions(); + if (assertions == null) { + assertions = new ArrayList<>(); + policy.setAssertions(assertions); + } + Assertion assertion = new Assertion(); + assertion.setRole(ZMSUtils.roleResourceName(domainName, rs.getString(ZMSConsts.DB_COLUMN_ROLE))); + assertion.setResource(rs.getString(ZMSConsts.DB_COLUMN_RESOURCE)); + assertion.setAction(rs.getString(ZMSConsts.DB_COLUMN_ACTION)); + assertion.setEffect(AssertionEffect.valueOf(rs.getString(ZMSConsts.DB_COLUMN_EFFECT))); + assertion.setId((long) rs.getInt(ZMSConsts.DB_COLUMN_ASSERT_ID)); + assertions.add(assertion); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + athenzDomain.getPolicies().addAll(policyMap.values()); + } + + void getAthenzDomainServices(String domainName, int domainId, AthenzDomain athenzDomain, String caller) { + + Map serviceMap = new HashMap<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_SERVICES)) { + ps.setInt(1, domainId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + String serviceName = rs.getString(ZMSConsts.DB_COLUMN_NAME); + ServiceIdentity service = new ServiceIdentity() + .setName(ZMSUtils.serviceResourceName(domainName, serviceName)) + .setProviderEndpoint(saveValue(rs.getString(ZMSConsts.DB_COLUMN_PROVIDER_ENDPOINT))) + .setExecutable(saveValue(rs.getString(ZMSConsts.DB_COLUMN_EXECTUABLE))) + .setUser(saveValue(rs.getString(ZMSConsts.DB_COLUMN_SVC_USER))) + .setGroup(saveValue(rs.getString(ZMSConsts.DB_COLUMN_SVC_GROUP))) + .setModified(Timestamp.fromMillis(rs.getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED).getTime())); + List publicKeys = new ArrayList<>(); + service.setPublicKeys(publicKeys); + serviceMap.put(serviceName, service); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_SERVICES_HOSTS)) { + ps.setInt(1, domainId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + String serviceName = rs.getString(1); + ServiceIdentity service = serviceMap.get(serviceName); + if (service == null) { + continue; + } + List hosts = service.getHosts(); + if (hosts == null) { + hosts = new ArrayList<>(); + service.setHosts(hosts); + } + hosts.add(rs.getString(2)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_SERVICES_PUBLIC_KEYS)) { + ps.setInt(1, domainId); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + String serviceName = rs.getString(1); + ServiceIdentity service = serviceMap.get(serviceName); + if (service == null) { + continue; + } + PublicKeyEntry publicKey = new PublicKeyEntry() + .setId(rs.getString(ZMSConsts.DB_COLUMN_KEY_ID)) + .setKey(rs.getString(ZMSConsts.DB_COLUMN_KEY_VALUE)); + service.getPublicKeys().add(publicKey); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + athenzDomain.getServices().addAll(serviceMap.values()); + } + + @Override + public AthenzDomain getAthenzDomain(String domainName) { + + final String caller = "getAthenzDomain"; + + int domainId = 0; + AthenzDomain athenzDomain = new AthenzDomain(domainName); + + try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN)) { + ps.setString(1, domainName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + Domain domain = saveDomainSettings(domainName, rs, caller); + athenzDomain.setDomain(domain); + domainId = rs.getInt(ZMSConsts.DB_COLUMN_DOMAIN_ID); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + if (domainId == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_DOMAIN, domainName); + } + + getAthenzDomainRoles(domainName, domainId, athenzDomain, caller); + getAthenzDomainPolicies(domainName, domainId, athenzDomain, caller); + getAthenzDomainServices(domainName, domainId, athenzDomain, caller); + + return athenzDomain; + } + + @Override + public DomainModifiedList listModifiedDomains(long modifiedSince) { + + final String caller = "listModifiedDomains"; + + DomainModifiedList domainModifiedList = new DomainModifiedList(); + List nameMods = new ArrayList(); + + try (PreparedStatement ps = prepareScanStatement(con, null, modifiedSince)) { + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + DomainModified dm = new DomainModified() + .setName(rs.getString(ZMSConsts.DB_COLUMN_NAME)) + .setModified(rs.getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED).getTime()); + nameMods.add(dm); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + domainModifiedList.setNameModList(nameMods); + return domainModifiedList; + } + + boolean validatePrincipalDomain(String principal) { + int idx = principal.lastIndexOf('.'); + if (idx == -1 || idx == 0 || idx == principal.length() - 1) { + return false; + } + if (getDomainId(con, principal.substring(0, idx)) == 0) { + return false; + } + return true; + } + + String roleIndex(String domainId, String roleName) { + StringBuilder index = new StringBuilder(512); + index.append(domainId).append(':').append(roleName); + return index.toString(); + } + + PreparedStatement prepareRoleAssertionsStatement(Connection con, String action) + throws SQLException { + + PreparedStatement ps = null; + if (action != null && action.length() > 0) { + ps = con.prepareStatement(SQL_LIST_ROLE_ASSERTIONS + SQL_LIST_ROLE_ASSERTION_QUERY_ACTION); + ps.setString(1, action); + } else { + ps = con.prepareStatement(SQL_LIST_ROLE_ASSERTIONS + SQL_LIST_ROLE_ASSERTION_NO_ACTION); + } + return ps; + } + + Map> getRoleAssertions(String action, String caller) { + + Map> roleAssertions = new HashMap<>(); + try (PreparedStatement ps = prepareRoleAssertionsStatement(con, action)) { + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + Assertion assertion = new Assertion(); + String domainName = rs.getString(ZMSConsts.DB_COLUMN_NAME); + String roleName = rs.getString(ZMSConsts.DB_COLUMN_ROLE); + assertion.setRole(ZMSUtils.roleResourceName(domainName, roleName)); + assertion.setResource(rs.getString(ZMSConsts.DB_COLUMN_RESOURCE)); + assertion.setAction(rs.getString(ZMSConsts.DB_COLUMN_ACTION)); + assertion.setEffect(AssertionEffect.valueOf(rs.getString(ZMSConsts.DB_COLUMN_EFFECT))); + assertion.setId((long) rs.getInt(ZMSConsts.DB_COLUMN_ASSERT_ID)); + + String index = roleIndex(rs.getString(ZMSConsts.DB_COLUMN_DOMAIN_ID), roleName); + List assertions = roleAssertions.get(index); + if (assertions == null) { + assertions = new ArrayList<>(); + roleAssertions.put(index, assertions); + } + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": adding assertion " + assertion + " for " + index); + } + + assertions.add(assertion); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + return roleAssertions; + } + + PreparedStatement prepareRolePrincipalsStatement(Connection con, String principal, boolean awsQuery) + throws SQLException { + + PreparedStatement ps = null; + if (principal != null && principal.length() > 0) { + ps = con.prepareStatement(SQL_LIST_ROLE_PRINCIPALS + SQL_LIST_ROLE_PRINCIPALS_QUERY); + ps.setString(1, principal); + } else if (awsQuery) { + ps = con.prepareStatement(SQL_LIST_ROLE_PRINCIPALS + SQL_LIST_ROLE_PRINCIPALS_YBY_ONLY); + } else { + ps = con.prepareStatement(SQL_LIST_ROLE_PRINCIPALS); + } + return ps; + } + + Map> getRolePrincipals(String principal, boolean awsQuery, String caller) { + + Map> rolePrincipals = new HashMap<>(); + try (PreparedStatement ps = prepareRolePrincipalsStatement(con, principal, awsQuery)) { + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + String principalName = rs.getString(ZMSConsts.DB_COLUMN_NAME); + String roleName = rs.getString(ZMSConsts.DB_COLUMN_ROLE_NAME); + + String index = roleIndex(rs.getString(ZMSConsts.DB_COLUMN_DOMAIN_ID), roleName); + List principals = rolePrincipals.get(index); + if (principals == null) { + principals = new ArrayList<>(); + rolePrincipals.put(index, principals); + } + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": adding principal " + principalName + " for " + index); + } + + principals.add(principalName); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + return rolePrincipals; + } + + Map> getTrustedRoles(String caller) { + + Map> trustedRoles = new HashMap<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_LIST_TRUSTED_ROLES)) { + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + String trustDomainId = rs.getString(ZMSConsts.DB_COLUMN_DOMAIN_ID); + String trustRoleName = rs.getString(ZMSConsts.DB_COLUMN_NAME); + String assertDomainId = rs.getString(ZMSConsts.DB_COLUMN_ASSERT_DOMAIN_ID); + String assertRoleName = rs.getString(ZMSConsts.DB_COLUMN_ROLE); + + String index = roleIndex(assertDomainId, assertRoleName); + List roles = trustedRoles.get(index); + if (roles == null) { + roles = new ArrayList<>(); + trustedRoles.put(index, roles); + } + + String tRoleName = roleIndex(trustDomainId, trustRoleName); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": adding trusted role " + tRoleName + " for " + index); + } + + roles.add(tRoleName); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + return trustedRoles; + } + + Map getAwsDomains(String caller) { + + Map awsDomains = new HashMap<>(); + try (PreparedStatement ps = con.prepareStatement(SQL_LIST_DOMAIN_AWS)) { + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": " + ps.toString()); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + awsDomains.put(rs.getString(ZMSConsts.DB_COLUMN_NAME), rs.getString(ZMSConsts.DB_COLUMN_ACCOUNT)); + } + } + } catch (SQLException ex) { + throw sqlError(ex, caller); + } + + return awsDomains; + } + + boolean skipAwsUserQuery(Map awsDomains, String queryPrincipal, + String rolePincipal, String userDomain) { + + // if no aws domains specified then it's not an aws query + + if (awsDomains == null) { + return false; + } + + // check if our query principal is not specified + + if (queryPrincipal != null && !queryPrincipal.isEmpty()) { + return false; + } + + // so now we know this is a global aws role query so we're only + // going to keep yby users - everyone else is skipped + + // make sure the principal starts with yby prefix + + String userDomainPrefix = userDomain + "."; + if (!rolePincipal.startsWith(userDomainPrefix)) { + return true; + } + + // make sure this is not a service within the user's + // personal domain + + if (rolePincipal.substring(userDomainPrefix.length()).indexOf('.') != -1) { + return true; + } + + return false; + } + + void addRoleAssertions(List principalAssertions, List roleAssertions, + Map awsDomains) { + + // if the role assertions is empty then we have nothing to do + + if (roleAssertions == null || roleAssertions.isEmpty()) { + + if (LOG.isDebugEnabled()) { + LOG.debug("addRoleAssertions: role assertion list is empty"); + } + + return; + } + + // if this is not an aws request or the awsDomain list is empty, + // then we're just going to add the role assertions to the + // principal's assertion list as is + + if (awsDomains == null || awsDomains.isEmpty()) { + principalAssertions.addAll(roleAssertions); + return; + } + + // we're going to update each assertion and generate the + // resource in the expected aws role format + + for (Assertion assertion : roleAssertions) { + + String resource = assertion.getResource(); + int idx = resource.indexOf(':'); + if (idx == -1) { + principalAssertions.add(assertion); + continue; + } + + String awsDomain = awsDomains.get(resource.substring(0, idx)); + if (awsDomain == null) { + principalAssertions.add(assertion); + continue; + } + + StringBuilder awsRole = new StringBuilder(512); + awsRole.append("arn:aws:iam::").append(awsDomain).append(":role/").append(resource.substring(idx + 1)); + assertion.setResource(awsRole.toString()); + principalAssertions.add(assertion); + } + } + + ResourceAccess getResourceAccessObject(String principal, List assertions) { + ResourceAccess rsrcAccess = new ResourceAccess(); + rsrcAccess.setPrincipal(principal); + rsrcAccess.setAssertions(assertions != null ? assertions : new ArrayList()); + return rsrcAccess; + } + + @Override + public ResourceAccessList listResourceAccess(String principal, String action, String userDomain) { + + final String caller = "listResourceAccess"; + + ResourceAccessList rsrcAccessList = new ResourceAccessList(); + List resources = new ArrayList<>(); + rsrcAccessList.setResources(resources); + + // check to see if this an aws request based on + // the action query + + boolean awsQuery = (action != null && action.equals(ZMSConsts.ACTION_ASSUME_AWS_ROLE)); + boolean singlePrincipalQuery = (principal != null && !principal.isEmpty()); + + // first let's get the principal list that we're asked to check for + // since if we have no matches then we have nothing to do + + Map> rolePrincipals = getRolePrincipals(principal, awsQuery, caller); + if (rolePrincipals.isEmpty()) { + if (singlePrincipalQuery) { + + // so the given principal is not available as a role member + // so before returning an empty response let's make sure + // that it has been registered in Athenz otherwise we'll + // just return 404 - not found exception + + if (getPrincipalId(con, principal) == 0) { + throw notFoundError(caller, ZMSConsts.OBJECT_PRINCIPAL, principal); + } + + resources.add(getResourceAccessObject(principal, null)); + } + return rsrcAccessList; + } + + // now let's get the list of role assertions. if we have + // no matches, then we have nothing to do + + Map> roleAssertions = getRoleAssertions(action, caller); + if (roleAssertions.isEmpty()) { + if (singlePrincipalQuery) { + resources.add(getResourceAccessObject(principal, null)); + } + return rsrcAccessList; + } + + // finally we need to get all the trusted role maps + + Map> trustedRoles = getTrustedRoles(caller); + + // couple of special cases - if we're asked for action assume_aws_role + // then we're looking for role access in AWS. So we're going to retrieve + // the domains that have aws account configured only and update + // the resource to generate aws role resources. If the action is + // assume_aws_role with no principal - then another special case to + // look for yby users only + + Map awsDomains = null; + if (awsQuery) { + awsDomains = getAwsDomains(caller); + } + + // now let's go ahead and combine all of our data together + // we're going to go through each principal, lookup + // the assertions for the role and add them to the return object + // if the role has no corresponding assertions, then we're going + // to look at the trust role map in case it's a trusted role + + Map> principalAssertions = new HashMap<>(); + for (Map.Entry> entry : rolePrincipals.entrySet()) { + + String roleIndex = entry.getKey(); + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": processing role: " + roleIndex); + } + + // get the list of principals for this role + + List rPrincipals = entry.getValue(); + for (String rPrincipal : rPrincipals) { + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": processing role principal: " + rPrincipal); + } + + // if running an aws query with no principals specified then make + // sure this is real user and not some service + + if (skipAwsUserQuery(awsDomains, principal, rPrincipal, userDomain)) { + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": skipping non-user: " + rPrincipal); + } + continue; + } + + List assertions = principalAssertions.get(rPrincipal); + if (assertions == null) { + assertions = new ArrayList<>(); + principalAssertions.put(rPrincipal, assertions); + } + + // retrieve the assertions for this role + + addRoleAssertions(assertions, roleAssertions.get(roleIndex), awsDomains); + + // check to see if this is a trusted role. There might be multiple + // roles all being mapped as trusted, so we need to process them all + + List mappedTrustedRoles = trustedRoles.get(roleIndex); + if (mappedTrustedRoles != null) { + for (String mappedTrustedRole : mappedTrustedRoles) { + + if (LOG.isDebugEnabled()) { + LOG.debug(caller + ": processing trusted role: " + mappedTrustedRole); + } + + addRoleAssertions(assertions, roleAssertions.get(mappedTrustedRole), awsDomains); + } + } + } + } + + // finally we need to create resource access list objects and return + + for (Map.Entry> entry : principalAssertions.entrySet()) { + + // if this is a query for all principals in Athenz then we're + // automatically going to skip any principals who have no + // assertions + + List assertions = entry.getValue(); + if (!singlePrincipalQuery && (assertions == null || assertions.isEmpty())) { + continue; + } + + resources.add(getResourceAccessObject(entry.getKey(), assertions)); + } + + return rsrcAccessList; + } + + RuntimeException notFoundError(String caller, String objectType, String objectName) { + rollbackChanges(); + String message = "unknown " + objectType + " - " + objectName; + return ZMSUtils.notFoundError(message, caller); + } + + RuntimeException requestError(String caller, String message) { + rollbackChanges(); + return ZMSUtils.requestError(message, caller); + } + + RuntimeException internalServerError(String caller, String message) { + rollbackChanges(); + return ZMSUtils.internalServerError(message, caller); + } + + RuntimeException sqlError(SQLException ex, String caller) { + + // check to see if this is a conflict error in which case + // we're going to let the server to retry the caller + // The two SQL states that are 'retry-able' are 08S01 + // for a communications error, and 40001 for deadlock. + // also check for the error code where the mysql server is + // in read-mode which could happen if we had a failover + // and the connections are still going to the old master + + String sqlState = ex.getSQLState(); + int code = ResourceException.INTERNAL_SERVER_ERROR; + String msg = null; + if ("08S01".equals(sqlState) || "40001".equals(sqlState)) { + code = ResourceException.CONFLICT; + msg = "Concurrent update conflict, please retry your operation later."; + } else if (ex.getErrorCode() == MYSQL_ER_OPTION_PREVENTS_STATEMENT) { + code = ResourceException.GONE; + msg = "MySQL Database running in read-only mode"; + } else if (ex.getErrorCode() == MYSQL_ER_OPTION_DUPLICATE_ENTRY) { + code = ResourceException.BAD_REQUEST; + msg = "Entry already exists"; + } else { + msg = ex.getMessage() + ", state: " + sqlState + ", code: " + ex.getErrorCode(); + } + rollbackChanges(); + return ZMSUtils.error(code, msg, caller); + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/JDBCObjectStore.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/JDBCObjectStore.java new file mode 100644 index 00000000000..6be3517f5cf --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/JDBCObjectStore.java @@ -0,0 +1,47 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.jdbc; + +import java.sql.SQLException; + +import com.yahoo.athenz.zms.ResourceException; +import com.yahoo.athenz.zms.store.ObjectStore; +import com.yahoo.athenz.zms.store.ObjectStoreConnection; +import com.yahoo.athenz.zms.utils.ZMSUtils; + +public class JDBCObjectStore implements ObjectStore { + + PoolableDataSource src; + + public JDBCObjectStore(PoolableDataSource src) { + this.src = src; + } + + @Override + public ObjectStoreConnection getConnection(boolean autoCommit) { + final String caller = "getConnection"; + try { + return new JDBCConnection(src.getConnection(), autoCommit); + } catch (SQLException ex) { + throw ZMSUtils.error(ResourceException.INTERNAL_SERVER_ERROR, caller, ex.getMessage()); + } + } + + @Override + public void clearConnections() { + src.clearPoolConnections(); + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/PoolableDataSource.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/PoolableDataSource.java new file mode 100644 index 00000000000..82692a56cee --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/PoolableDataSource.java @@ -0,0 +1,26 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.jdbc; + +import javax.sql.DataSource; + +public interface PoolableDataSource extends DataSource { + + /** + * Clears all idle connections in the pool + */ + public void clearPoolConnections(); +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/ZMSDataSource.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/ZMSDataSource.java new file mode 100644 index 00000000000..7bba4a0f6c0 --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/store/jdbc/ZMSDataSource.java @@ -0,0 +1,46 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.jdbc; + +import org.apache.commons.dbcp2.PoolableConnection; +import org.apache.commons.dbcp2.PoolingDataSource; +import org.apache.commons.pool2.ObjectPool; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ZMSDataSource extends PoolingDataSource implements PoolableDataSource { + + private static final Logger LOG = LoggerFactory.getLogger(ZMSDataSource.class); + + public ZMSDataSource(ObjectPool pool) { + super(pool); + } + + @Override + synchronized public void clearPoolConnections() { + ObjectPool pool = getPool(); + try { + if (LOG.isDebugEnabled()) { + LOG.debug("Clearing all active/idle (" + pool.getNumActive() + "/" + + pool.getNumIdle() + ") connections from the pool"); + } + pool.clear(); + } catch (Exception ex) { + LOG.error("Unable to clear connections from the pool: " + ex.getMessage()); + } + } +} diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/utils/ZMSUtils.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/utils/ZMSUtils.java new file mode 100644 index 00000000000..fab9fdd27fc --- /dev/null +++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/utils/ZMSUtils.java @@ -0,0 +1,216 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.utils; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.common.server.util.StringUtils; +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.AssertionEffect; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.ResourceError; +import com.yahoo.athenz.zms.ResourceException; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.ZMSConsts; +import com.yahoo.athenz.zms.ZMSImpl; + +public class ZMSUtils { + + private static final Logger LOG = LoggerFactory.getLogger(ZMSUtils.class); + + public static void addAssertion(Policy policy, String resource, String action, String role, + AssertionEffect effect) { + + List assertions = policy.getAssertions(); + if (assertions == null) { + assertions = new ArrayList<>(); + policy.setAssertions(assertions); + } + Assertion assertion = new Assertion() + .setAction(action) + .setResource(resource) + .setRole(role); + assertions.add(assertion); + } + + public static Role makeAdminRole(String domainName, List adminUsers) { + + Role role = new Role() + .setName(roleResourceName(domainName, ZMSConsts.ADMIN_ROLE_NAME)) + .setMembers(adminUsers); + return role; + } + + public static Policy makeAdminPolicy(String domainName, Role adminsRole) { + + Policy policy = new Policy() + .setName(policyResourceName(domainName, ZMSConsts.ADMIN_POLICY_NAME)); + + addAssertion(policy, domainName + ":*", "*", adminsRole.getName(), AssertionEffect.ALLOW); + return policy; + } + + static String generateResourceName(String domainName, String resName, String resType) { + StringBuilder name = new StringBuilder(ZMSConsts.STRING_BLDR_SIZE_DEFAULT).append(domainName); + if (!resType.isEmpty()) { + name.append(':'); + name.append(resType); + } + name.append('.'); + name.append(resName); + return name.toString(); + } + + public static String roleResourceName(String domainName, String roleName) { + return generateResourceName(domainName, roleName, ZMSConsts.OBJECT_ROLE); + } + + public static String policyResourceName(String domainName, String policyName) { + return generateResourceName(domainName, policyName, ZMSConsts.OBJECT_POLICY); + } + + public static String serviceResourceName(String domainName, String serviceName) { + return generateResourceName(domainName, serviceName, ""); + } + + public static String entityResourceName(String domainName, String serviceName) { + return generateResourceName(domainName, serviceName, ""); + } + + public static String removeDomainPrefix(String objectName, String domainName, String objectPrefix) { + String valPrefix = domainName + ":" + objectPrefix; + if (objectName.startsWith(valPrefix)) { + objectName = objectName.substring(valPrefix.length()); + } + return objectName; + } + + public static String getTenantResourceGroupRolePrefix(String provSvcName, String tenantDomain, String resourceGroup) { + + StringBuilder rolePrefix = new StringBuilder(256); + rolePrefix.append(provSvcName).append(".tenant.").append(tenantDomain).append("."); + if (resourceGroup != null) { + rolePrefix.append("res_group.").append(resourceGroup).append("."); + } + return rolePrefix.toString(); + } + + public static String getProviderResourceGroupRolePrefix(String provSvcDomain, String provSvcName, String resourceGroup) { + + StringBuilder rolePrefix = new StringBuilder(256); + rolePrefix.append(provSvcDomain).append(".").append(provSvcName).append("."); + if (resourceGroup != null) { + rolePrefix.append("res_group.").append(resourceGroup).append("."); + } + return rolePrefix.toString(); + } + + public static String getTrustedResourceGroupRolePrefix(String provSvcDomain, String provSvcName, + String tenantDomain, String resourceGroup) { + + StringBuilder trustedRole = new StringBuilder(256); + trustedRole.append(provSvcDomain).append(":role.").append(provSvcName) + .append(".tenant.").append(tenantDomain).append("."); + if (resourceGroup != null) { + trustedRole.append("res_group.").append(resourceGroup).append("."); + } + return trustedRole.toString(); + } + + public static boolean assumeRoleResourceMatch(String roleName, Assertion assertion) { + + if (!ZMSConsts.ACTION_ASSUME_ROLE.equalsIgnoreCase(assertion.getAction())) { + return false; + } + + String rezPattern = StringUtils.patternFromGlob(assertion.getResource()); + if (!roleName.matches(rezPattern)) { + return false; + } + + return true; + } + + public static RuntimeException error(int code, String msg, String caller) { + + if (LOG.isDebugEnabled()) { + LOG.debug(msg); + } + + // If caller is null, we do not want to emit any error metrics. + // Otherwise, the caller name should be from the method that threw + // the specific runtime exception. + + if (caller != null && !emitMonmetricError(code, caller)) { + LOG.error("Unable to emit error metric for caller: " + caller + + " with message: " + msg); + } + return new ResourceException(code, new ResourceError().code(code).message(msg)); + } + + public static RuntimeException requestError(String msg, String caller) { + return error(ResourceException.BAD_REQUEST, msg, caller); + } + + public static RuntimeException redirectError(String msg, String caller) { + return error(ResourceException.FOUND, msg, caller); + } + + public static RuntimeException unauthorizedError(String msg, String caller) { + return error(ResourceException.UNAUTHORIZED, msg, caller); + } + + public static RuntimeException forbiddenError(String msg, String caller) { + return error(ResourceException.FORBIDDEN, msg, caller); + } + + public static RuntimeException notFoundError(String msg, String caller) { + return error(ResourceException.NOT_FOUND, msg, caller); + } + + public static RuntimeException internalServerError(String msg, String caller) { + return error(ResourceException.INTERNAL_SERVER_ERROR, msg, caller); + } + + public static boolean emitMonmetricError(int errorCode, String caller) { + if (errorCode < 1) { + return false; + } + if (caller == null || caller.length() == 0) { + return false; + } + caller = caller.trim(); + String alphanum = "^[a-zA-Z0-9]*$"; + if (!caller.matches(alphanum)) { + return false; + } + + // Set 3 scoreboard error metrics: + // (1) cumulative "ERROR" (of all zms request and error types) + // (2) cumulative granular zms request and error type (eg- "getdomainlist_error_400") + // (3) cumulative error type (of all zms requests) (eg- "error_404") + String errCode = Integer.toString(errorCode); + ZMSImpl.metric.increment("ERROR"); + ZMSImpl.metric.increment(caller.toLowerCase() + "_error_" + errCode); + ZMSImpl.metric.increment("error_" + errCode); + + return true; + } +} diff --git a/servers/zms/src/main/rdl/ZMS.rdl b/servers/zms/src/main/rdl/ZMS.rdl new file mode 100644 index 00000000000..3fe055b080a --- /dev/null +++ b/servers/zms/src/main/rdl/ZMS.rdl @@ -0,0 +1,17 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +// +//The Authorization Management Service (ZMS) API +// +name ZMS; +use "rdl"; +version 1; +namespace com.yahoo.athenz.zms; + +include "../../../../../core/zms/src/main/rdl/ZMS.rdl"; + +// Get RDL Schema +resource rdl.Schema GET "/schema" { + expected OK; +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/provider/ProviderMockClient.java b/servers/zms/src/test/java/com/yahoo/athenz/provider/ProviderMockClient.java new file mode 100644 index 00000000000..4ac11df027e --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/provider/ProviderMockClient.java @@ -0,0 +1,96 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.provider; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.yahoo.athenz.auth.Principal; + +public class ProviderMockClient extends ProviderClient { + + private static boolean returnTenantRoles = true; + private static List resourceGroups = new ArrayList<>(); + public ProviderMockClient(String url, Principal identity) { + super(url); + } + + static final List TABLE_PROVIDER_ROLES = Arrays.asList("admin", + "writer", "reader"); + + static final List RESOURCE_PROVIDER_ROLES = Arrays.asList("writer", "reader"); + + @Override + public Tenant getTenant(String providerService, String tenantDomain) { + Tenant tenant = new Tenant(); + tenant.setName(tenantDomain).setService(providerService); + if (!resourceGroups.isEmpty()) { + tenant.setResourceGroups(resourceGroups); + } + return tenant; + } + + @Override + public Tenant deleteTenant(String providerService, String tenantDomain, String auditRef) { + return null; + } + + @Override + public Tenant putTenant(String providerService, String tenantDomain, String auditRef, Tenant spec) { + + System.out.println("putTenant: " + tenantDomain + " -> " + spec); + if (!tenantDomain.equals(spec.getName())) { + return null; + } + + if (returnTenantRoles) { + spec.setRoles(TABLE_PROVIDER_ROLES); + } + return spec; + } + + @Override + public TenantResourceGroup deleteTenantResourceGroup(String providerService, String tenantDomain, String resourceGroup, + String auditRef) { + return null; + } + + @Override + public TenantResourceGroup putTenantResourceGroup(String providerService, String tenantDomain, String resourceGroup, + String auditRef, TenantResourceGroup data) { + + System.out.println("putTenantResourceGroup: " + tenantDomain + " -> " + data); + if (!tenantDomain.equals(data.getName())) { + return null; + } + + data.setRoles(RESOURCE_PROVIDER_ROLES); + return data; + } + + public static void setReturnTenantRoles(boolean returnTenantRoles) { + ProviderMockClient.returnTenantRoles = returnTenantRoles; + } + + public static void setResourceGroups(List resourceGroups) { + if (resourceGroups == null) { + ProviderMockClient.resourceGroups.clear(); + } else { + ProviderMockClient.resourceGroups.addAll(resourceGroups); + } + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/provider/ProviderMockClientTest.java b/servers/zms/src/test/java/com/yahoo/athenz/provider/ProviderMockClientTest.java new file mode 100644 index 00000000000..d107b54ae28 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/provider/ProviderMockClientTest.java @@ -0,0 +1,50 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.provider; + +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.SimplePrincipal; + +public class ProviderMockClientTest { + + @Test + public void testPutTenant() { + String systemAdminUser = "user.user_admin"; + Authority authority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + Principal p = SimplePrincipal.create("user", systemAdminUser, + "v=U1;d=user;n=" + systemAdminUser + ";s=signature", 0, authority); + ProviderMockClient provider = new ProviderMockClient("localhost:3306/athenz", p); + Tenant tenant = new Tenant(); + tenant.setName("name"); + assertNull(provider.putTenant("providerService1", "tenantDom1", "zms", tenant)); + } + + @Test + public void testPutTenantResourceGroup() { + String systemAdminUser = "user.user_admin"; + Authority authority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + Principal p = SimplePrincipal.create("user", systemAdminUser, + "v=U1;d=user;n=" + systemAdminUser + ";s=signature", 0, authority); + ProviderMockClient provider = new ProviderMockClient("localhost:3306/athenz", p); + TenantResourceGroup tenant = new TenantResourceGroup(); + tenant.setName("name"); + assertNull(provider.putTenantResourceGroup("providerService1", "tenantDom1", "zms", "zms", tenant)); + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/DBServiceTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/DBServiceTest.java new file mode 100644 index 00000000000..d49abcce0b8 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/DBServiceTest.java @@ -0,0 +1,2498 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import org.mockito.Mockito; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.*; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.server.log.AuditLogFactory; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.provider.ProviderMockClient; +import com.yahoo.athenz.zms.store.ObjectStore; +import com.yahoo.athenz.zms.store.file.FileConnection; +import com.yahoo.athenz.zms.store.file.FileObjectStore; +import com.yahoo.athenz.zms.utils.ZMSUtils; +import com.yahoo.rdl.Struct; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +public class DBServiceTest extends TestCase { + + @Mock FileConnection mockFileConn; + @Mock ObjectStore mockObjStore; + + ZMSImpl zms = null; + String adminUser = null; + String pubKey = null; // assume default is K0 + String pubKeyK1 = null; + String pubKeyK2 = null; + String privKey = null; // assume default is K0 + String privKeyK1 = null; + String privKeyK2 = null; + String auditRef = "audittest"; + + // typically used when creating and deleting domains with all the tests + // + @Mock ZMSImpl.RsrcCtxWrapper mockDomRsrcCtx; + @Mock com.yahoo.athenz.common.server.rest.ResourceContext mockDomRestRsrcCtx; + Principal rsrcPrince = null; // used with the mockDomRestRsrcCtx + + private static final String MOCKCLIENTADDR = "10.11.12.13"; + @Mock HttpServletRequest mockServletRequest; + @Mock HttpServletResponse mockServletResponse; + + private static final String ZMS_DATA_STORE_PATH = "/tmp/zms_core_unit_tests/zms_root"; + + static final Struct TABLE_PROVIDER_ROLE_ACTIONS = new Struct() + .with("admin", "*").with("writer", "WRITE").with("reader", "READ"); + + static final Struct RESOURCE_PROVIDER_ROLE_ACTIONS = new Struct() + .with("writer", "WRITE").with("reader", "READ"); + + static final int BASE_PRODUCT_ID = 500000000; // these product ids will lie in 500 million range + static java.util.Random domainProductId = new java.security.SecureRandom(); + static synchronized int getRandomProductId() { + return BASE_PRODUCT_ID + domainProductId.nextInt(99999999); + } + + @BeforeClass + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Mockito.when(mockServletRequest.getRemoteAddr()).thenReturn(MOCKCLIENTADDR); + setupServiceId(); + } + + ResourceContext createResourceContext(Principal prince) { + com.yahoo.athenz.common.server.rest.ResourceContext rsrcCtx = Mockito.mock(com.yahoo.athenz.common.server.rest.ResourceContext.class); + Mockito.when(rsrcCtx.principal()).thenReturn(prince); + Mockito.when(rsrcCtx.request()).thenReturn(mockServletRequest); + Mockito.when(rsrcCtx.response()).thenReturn(mockServletResponse); + + ZMSImpl.RsrcCtxWrapper rsrcCtxWrapper = Mockito.mock(ZMSImpl.RsrcCtxWrapper.class); + Mockito.when(rsrcCtxWrapper.context()).thenReturn(rsrcCtx); + Mockito.when(rsrcCtxWrapper.principal()).thenReturn(prince); + Mockito.when(rsrcCtxWrapper.request()).thenReturn(mockServletRequest); + Mockito.when(rsrcCtxWrapper.response()).thenReturn(mockServletResponse); + return rsrcCtxWrapper; + } + + ResourceContext createResourceContext(Principal principal, HttpServletRequest request) { + if (request == null) { + return createResourceContext(principal); + } + + com.yahoo.athenz.common.server.rest.ResourceContext rsrcCtx = Mockito.mock(com.yahoo.athenz.common.server.rest.ResourceContext.class); + Mockito.when(rsrcCtx.principal()).thenReturn(principal); + Mockito.when(rsrcCtx.request()).thenReturn(request); + Mockito.when(rsrcCtx.response()).thenReturn(mockServletResponse); + + ZMSImpl.RsrcCtxWrapper rsrcCtxWrapper = Mockito.mock(ZMSImpl.RsrcCtxWrapper.class); + Mockito.when(rsrcCtxWrapper.context()).thenReturn(rsrcCtx); + Mockito.when(rsrcCtxWrapper.request()).thenReturn(request); + Mockito.when(rsrcCtxWrapper.principal()).thenReturn(principal); + Mockito.when(rsrcCtxWrapper.response()).thenReturn(mockServletResponse); + return rsrcCtxWrapper; + } + + Object getWebAppExcEntity(javax.ws.rs.WebApplicationException wex) { + javax.ws.rs.core.Response resp = wex.getResponse(); + return resp.getEntity(); + } + + Object getWebAppExcMapValue(javax.ws.rs.WebApplicationException wex, String header) { + javax.ws.rs.core.MultivaluedMap mvmap = wex.getResponse().getMetadata(); + Object obj = mvmap.getFirst(header); + return obj; + } + + private ZMSImpl zmsInit() { + // we want to make sure we start we clean dir structure + FileConnection.deleteDirectory(new File(ZMS_DATA_STORE_PATH)); + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + + rsrcPrince = SimplePrincipal.create("user", "user1", "v=U1;d=user;n=user1;s=signature", + 0, principalAuthority); + ((SimplePrincipal) rsrcPrince).setUnsignedCreds("v=U1;d=user;n=user1"); + + Mockito.when(mockDomRestRsrcCtx.request()).thenReturn(mockServletRequest); + Mockito.when(mockDomRestRsrcCtx.principal()).thenReturn(rsrcPrince); + Mockito.when(mockDomRsrcCtx.context()).thenReturn(mockDomRestRsrcCtx); + Mockito.when(mockDomRsrcCtx.request()).thenReturn(mockServletRequest); + Mockito.when(mockDomRsrcCtx.principal()).thenReturn(rsrcPrince); + + ObjectStore store = new FileObjectStore(new File(ZMS_DATA_STORE_PATH)); + + String pubKeyName = System.getProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY); + System.out.println("public key file=" + pubKeyName); + File pubKeyFile = new File(pubKeyName); + pubKey = Crypto.encodedFile(pubKeyFile); + + String privKeyName = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY); + System.out.println("private key file=" + privKeyName); + File privKeyFile = new File(privKeyName); + privKey = Crypto.encodedFile(privKeyFile); + PrivateKey privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + + String privKeyId = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_ID, "0"); + + adminUser = System.getProperty(ZMSConsts.ZMS_PROP_DOMAIN_ADMIN); + + // enable product id support + System.setProperty(ZMSConsts.ZMS_PROP_PRODUCT_ID_SUPPORT, "true"); + System.setProperty(ZMSConsts.ZMS_PROP_SOLUTION_TEMPLATE_FNAME, "src/test/resources/solution_templates.json"); + + Metric debugMetric = new com.yahoo.athenz.common.metrics.impl.NoOpMetric(); + ZMSImpl zmsObj = new ZMSImpl("localhost", store, debugMetric, privateKey, + privKeyId, pubKey, AuditLogFactory.getLogger(), null); + + ServiceIdentity service = createServiceObject("sys.auth", + "zms", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zmsObj.putServiceIdentity(mockDomRsrcCtx, "sys.auth", "zms", auditRef, service); + + return zmsObj; + } + + private Entity createEntityObject(String entityName) { + + Entity entity = new Entity(); + entity.setName(entityName); + + Struct value = new Struct(); + value.put("Key1", "Value1"); + entity.setValue(value); + + return entity; + } + + private Role createRoleObject(String domainName, String roleName, + String trust, String member1, String member2) { + + List members = new ArrayList(); + if (member1 != null) { + members.add(member1); + } + if (member2 != null) { + members.add(member2); + } + return createRoleObject(domainName, roleName, trust, members); + } + + private Role createRoleObject(String domainName, String roleName, + String trust, List members) { + + Role role = new Role(); + role.setName(ZMSUtils.roleResourceName(domainName, roleName)); + role.setMembers(members); + if (trust != null) { + role.setTrust(trust); + } + + return role; + } + + private Policy createPolicyObject(String domainName, String policyName) { + return createPolicyObject(domainName, policyName, "role1", true, "*", + domainName + ":*", AssertionEffect.ALLOW); + } + + private Policy createPolicyObject(String domainName, String policyName, + String roleName, boolean generateRoleName, String action, + String resource, AssertionEffect effect) { + + Policy policy = new Policy(); + policy.setName(ZMSUtils.policyResourceName(domainName, policyName)); + + Assertion assertion = new Assertion(); + assertion.setAction(action); + assertion.setEffect(effect); + assertion.setResource(resource); + if (generateRoleName) { + assertion.setRole(ZMSUtils.roleResourceName(domainName, roleName)); + } else { + assertion.setRole(roleName); + } + + List assertList = new ArrayList(); + assertList.add(assertion); + + policy.setAssertions(assertList); + return policy; + } + + private ServiceIdentity createServiceObject(String domainName, + String serviceName, String endPoint, String executable, + String user, String group, String host) { + + ServiceIdentity service = new ServiceIdentity(); + service.setExecutable(executable); + service.setName(ZMSUtils.serviceResourceName(domainName, serviceName)); + + List publicKeyList = new ArrayList(); + PublicKeyEntry publicKeyEntry1 = new PublicKeyEntry(); + publicKeyEntry1.setKey(pubKeyK1); + publicKeyEntry1.setId("1"); + publicKeyList.add(publicKeyEntry1); + PublicKeyEntry publicKeyEntry2 = new PublicKeyEntry(); + publicKeyEntry2.setKey(pubKeyK2); + publicKeyEntry2.setId("2"); + publicKeyList.add(publicKeyEntry2); + service.setPublicKeys(publicKeyList); + + service.setUser(user); + service.setGroup(group); + + if (endPoint != null) { + service.setProviderEndpoint(endPoint); + } + + List hosts = new ArrayList(); + hosts.add(host); + service.setHosts(hosts); + + return service; + } + + ZMSImpl getZmsImpl(String storeDir, AuditLogger alogger) { + + FileConnection.deleteDirectory(new File(storeDir)); + ObjectStore store = new FileObjectStore(new File(storeDir)); + + String privKeyName = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY); + File privKeyFile = new File(privKeyName); + String privKey = Crypto.encodedFile(privKeyFile); + PrivateKey privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + + String privKeyId = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_ID, "0"); + ServiceIdentity service = createServiceObject("sys.auth", + "zms", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + Metric debugMetric = new com.yahoo.athenz.common.metrics.impl.NoOpMetric(); + ZMSImpl zmsObj = new ZMSImpl("localhost", store, debugMetric, privateKey, + privKeyId, pubKey, alogger, null); + zmsObj.putServiceIdentity(mockDomRsrcCtx, "sys.auth", "zms", auditRef, service); + zmsObj.setProviderClientClass(ProviderMockClient.class); + return zmsObj; + } + + public void setupServiceId() throws IOException { + + Path path = Paths.get("./src/test/resources/zms_public_k1.pem"); + pubKeyK1 = Crypto.ybase64((new String(Files.readAllBytes(path))).getBytes()); + + path = Paths.get("./src/test/resources/zms_public_k2.pem"); + pubKeyK2 = Crypto.ybase64(new String(Files.readAllBytes(path)).getBytes()); + + path = Paths.get("./src/test/resources/zms_private_k1.pem"); + privKeyK1 = Crypto.ybase64(new String(Files.readAllBytes(path)).getBytes()); + + path = Paths.get("./src/test/resources/zms_private_k2.pem"); + privKeyK2 = Crypto.ybase64(new String(Files.readAllBytes(path)).getBytes()); + + zms = zmsInit(); + zms.setProviderClientClass(ProviderMockClient.class); + } + + @AfterClass + public void shutdown() { + FileConnection.deleteDirectory(new File(ZMS_DATA_STORE_PATH)); + System.clearProperty(ZMSConsts.ZMS_PROP_PRODUCT_ID_SUPPORT); + } + + private TopLevelDomain createTopLevelDomainObject(String name, String description, + String org, String admin, boolean enabled, boolean auditEnabled) { + + TopLevelDomain dom = new TopLevelDomain(); + dom.setName(name); + dom.setDescription(description); + dom.setOrg(org); + dom.setAuditEnabled(auditEnabled); + dom.setEnabled(enabled); + dom.setYpmId(getRandomProductId()); + + List admins = new ArrayList(); + admins.add(admin); + dom.setAdminUsers(admins); + + return dom; + } + + private TopLevelDomain createTopLevelDomainObject(String name, + String description, String org, String admin) { + return createTopLevelDomainObject(name, description, org, admin, true, false); + } + + @Test + public void testCheckDomainAuditEnabledFlagTrueRefValid() { + + String domainName = "audit-test-domain-name"; + Domain domain = new Domain().setAuditEnabled(true).setEnabled(true); + Mockito.doReturn(domain).when(mockFileConn).getDomain(domainName); + + String auditCheck = "testaudit"; + String caller = "testCheckDomainAuditEnabledFlagTrueRefValid"; + Domain dom = zms.dbService.checkDomainAuditEnabled(mockFileConn, domainName, auditCheck, caller); + assertNotNull(dom); + } + + @Test + public void testGetResourceAccessList() { + try { + // currently in the filestore that we're using for our unit + // we don't have an implementation for this method + zms.dbService.getResourceAccessList("principal", "UPDATE"); + } catch (Exception ex) { + assertTrue(true); + } + } + + private SubDomain createSubDomainObject(String name, String parent, + String description, String org, String admin) { + + SubDomain dom = new SubDomain(); + dom.setName(name); + dom.setDescription(description); + dom.setOrg(org); + dom.setParent(parent); + + List admins = new ArrayList(); + admins.add(admin); + dom.setAdminUsers(admins); + + return dom; + } + + @Test + public void testCheckDomainAuditEnabledFlagTrueRefNull() { + + String domainName = "audit-test-domain-name"; + Domain domain = new Domain().setAuditEnabled(true).setEnabled(true); + Mockito.doReturn(domain).when(mockFileConn).getDomain(domainName); + + String auditCheck = null; + String caller = "testCheckDomainAuditEnabledFlagTrueRefNull"; + try { + zms.dbService.checkDomainAuditEnabled(mockFileConn, domainName, auditCheck, caller); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } + } + + @Test + public void testCheckDomainAuditEnabledFlagTrueRefEmpty() { + + String domainName = "audit-test-domain-name"; + Domain domain = new Domain().setAuditEnabled(true).setEnabled(true); + Mockito.doReturn(domain).when(mockFileConn).getDomain(domainName); + + String auditCheck = ""; // empty string + String caller = "testCheckDomainAuditEnabledFlagTrueRefEmpty"; + try { + zms.dbService.checkDomainAuditEnabled(mockFileConn, domainName, auditCheck, caller); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } + } + + @Test + public void testCheckDomainAuditEnabledFlagFalseRefValid() { + + String domainName = "audit-test-domain-name"; + Domain domain = new Domain().setAuditEnabled(false).setEnabled(true); + Mockito.doReturn(domain).when(mockFileConn).getDomain(domainName); + + String auditCheck = "testaudit"; + String caller = "testCheckDomainAuditEnabledFlagFalseRefValid"; + Domain dom = zms.dbService.checkDomainAuditEnabled(mockFileConn, domainName, auditCheck, caller); + assertNotNull(dom); + } + + @Test + public void testCheckDomainAuditEnabledFlagFalseRefNull() { + + String domainName = "audit-test-domain-name"; + Domain domain = new Domain().setAuditEnabled(false).setEnabled(true); + Mockito.doReturn(domain).when(mockFileConn).getDomain(domainName); + + String auditCheck = null; + String caller = "testCheckDomainAuditEnabledFlagFalseRefNull"; + Domain dom = zms.dbService.checkDomainAuditEnabled(mockFileConn, domainName, auditCheck, caller); + assertNotNull(dom); + } + + @Test + public void testCheckDomainAuditEnabledFlagFalseRefEmpty() { + + String domainName = "audit-test-domain-name"; + Domain domain = new Domain().setAuditEnabled(false).setEnabled(true); + Mockito.doReturn(domain).when(mockFileConn).getDomain(domainName); + + String auditCheck = ""; + String caller = "testCheckDomainAuditEnabledFlagFalseRefEmpty"; + Domain dom = zms.dbService.checkDomainAuditEnabled(mockFileConn, domainName, auditCheck, caller); + assertNotNull(dom); + } + + @Test + public void testCheckDomainAuditEnabledInvalidDomain() { + + String domainName = "audit-test-domain-name"; + Domain domain = new Domain().setAuditEnabled(false).setEnabled(true); + Mockito.doReturn(domain).when(mockFileConn).getDomain(domainName); + + String auditCheck = "testaudit"; + String caller = "testCheckDomainAuditEnabledDefault"; + try { + zms.dbService.checkDomainAuditEnabled(mockFileConn, "unknown_domain", auditCheck, caller); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testUpdateTemplateRoleNoMembers() { + Role role = new Role().setName("_domain_:role.readers"); + Role newRole = zms.dbService.updateTemplateRole(role, "athenz", "readers"); + assertEquals("athenz:role.readers", newRole.getName()); + assertEquals(0, newRole.getMembers().size()); + } + + @Test + public void testUpdateTemplateRoleWithTrust() { + Role role = new Role().setName("_domain_:role.readers").setTrust("trustdomain"); + Role newRole = zms.dbService.updateTemplateRole(role, "athenz", "readers"); + assertEquals("athenz:role.readers", newRole.getName()); + assertEquals("trustdomain", newRole.getTrust()); + assertEquals(0, newRole.getMembers().size()); + } + + @Test + public void testUpdateTemplateRoleWithMembers() { + Role role = new Role().setName("_domain_:role.readers"); + List members = new ArrayList<>(); + members.add("user.user1"); + members.add("user.user2"); + role.setMembers(members); + + Role newRole = zms.dbService.updateTemplateRole(role, "athenz", "readers"); + assertEquals("athenz:role.readers", newRole.getName()); + List newMembers = newRole.getMembers(); + assertEquals(2, newMembers.size()); + assertTrue(newMembers.contains("user.user1")); + assertTrue(newMembers.contains("user.user2")); + } + + @Test + public void testUpdateTemplatePolicy() { + Policy policy = createPolicyObject("_domain_", "policy1", + "role1", true, "read", "_domain_:*", AssertionEffect.ALLOW); + + Policy newPolicy = zms.dbService.updateTemplatePolicy(policy, "athenz", "policy1"); + + assertEquals("athenz:policy.policy1", newPolicy.getName()); + + List assertions = newPolicy.getAssertions(); + assertEquals(1, assertions.size()); + Assertion assertion = assertions.get(0); + assertEquals("athenz:role.role1", assertion.getRole()); + assertEquals("athenz:*", assertion.getResource()); + assertEquals("read", assertion.getAction()); + assertEquals(AssertionEffect.ALLOW, assertion.getEffect()); + } + + @Test + public void testUpdateTemplatePolicyNoAssertions() { + Policy policy = new Policy().setName("_domain_:policy.policy1"); + Policy newPolicy = zms.dbService.updateTemplatePolicy(policy, "athenz", "policy1"); + + assertEquals("athenz:policy.policy1", newPolicy.getName()); + List assertions = newPolicy.getAssertions(); + assertEquals(0, assertions.size()); + } + + @Test + public void testUpdateTemplatePolicyAssertionNoRewrite() { + Policy policy = createPolicyObject("_domain_", "policy1", + "coretech:role.role1", false, "read", "coretech:*", AssertionEffect.ALLOW); + + Policy newPolicy = zms.dbService.updateTemplatePolicy(policy, "athenz", "policy1"); + + assertEquals("athenz:policy.policy1", newPolicy.getName()); + + List assertions = newPolicy.getAssertions(); + assertEquals(1, assertions.size()); + Assertion assertion = assertions.get(0); + assertEquals("coretech:role.role1", assertion.getRole()); + assertEquals("coretech:*", assertion.getResource()); + assertEquals("read", assertion.getAction()); + assertEquals(AssertionEffect.ALLOW, assertion.getEffect()); + } + + @Test + public void testIsTenantRolePrefixMatchNoPrefixMatch() { + assertFalse(zms.dbService.isTenantRolePrefixMatch(mockFileConn, "coretech.storage.role1", + "coretech2.role.", "tenant")); + } + + @Test + public void testIsTenantRolePrefixMatchResGroupNullTenant() { + assertFalse(zms.dbService.isTenantRolePrefixMatch(mockFileConn, "coretech.storage.res_group.reader", + "coretech.storage.", null)); + } + + @Test + public void testIsTenantRolePrefixMatchResGroupMultipleComponents() { + assertFalse(zms.dbService.isTenantRolePrefixMatch(mockFileConn, "coretech.storage.group1.group2.group3.reader", + "coretech.storage.", "tenant")); + } + + @Test + public void testIsTenantRolePrefixMatchResGroupSingleComponent() { + assertTrue(zms.dbService.isTenantRolePrefixMatch(mockFileConn, "coretech.storage.group1", + "coretech.storage.", "tenant")); + } + + @Test + public void testIsTenantRolePrefixMatchSubdomainCheckExists() { + + Domain domain = new Domain().setAuditEnabled(false).setEnabled(true); + Mockito.doReturn(domain).when(mockFileConn).getDomain("tenant.sub"); + + // since subdomain exists - we're assuming is not a tenant role + + assertFalse(zms.dbService.isTenantRolePrefixMatch(mockFileConn, "coretech.storage.sub.reader", + "coretech.storage.", "tenant")); + } + + @Test + public void testIsTenantRolePrefixMatchSubdomainCheckDoesNotExist() { + + Mockito.doReturn(null).when(mockFileConn).getDomain("tenant.sub"); + + // subdomain does not exist thus this is a tenant role + + assertTrue(zms.dbService.isTenantRolePrefixMatch(mockFileConn, "coretech.storage.sub.reader", + "coretech.storage.", "tenant")); + } + + @Test + public void testIsTrustRoleForTenantPrefixNoMatch() { + + assertFalse(zms.dbService.isTrustRoleForTenant(mockFileConn, "sports", "coretech.storage.tenant.admin", + "coretech2.storage.tenant.", "athenz")); + } + + @Test + public void testIsTrustRoleForTenantNoRole() { + + Mockito.doReturn(null).when(mockFileConn).getRole("sports", "coretech.storage.tenant.admin"); + + assertFalse(zms.dbService.isTrustRoleForTenant(mockFileConn, "sports", "coretech.storage.tenant.admin", + "coretech.storage.tenant.", "athenz")); + } + + @Test + public void testIsTrustRoleForTenantNoRoleTrust() { + + Role role = new Role().setName(ZMSUtils.roleResourceName("sports", "coretech.storage.tenant.admin")); + Mockito.doReturn(role).when(mockFileConn).getRole("sports", "coretech.storage.tenant.admin"); + + assertFalse(zms.dbService.isTrustRoleForTenant(mockFileConn, "sports", "coretech.storage.tenant.admin", + "coretech.storage.tenant.", "athenz")); + } + + @Test + public void testIsTrustRoleForTenantRoleTrustMatch() { + + Role role = new Role().setName(ZMSUtils.roleResourceName("sports", "coretech.storage.tenant.admin")) + .setTrust("athenz"); + Mockito.doReturn(role).when(mockFileConn).getRole("sports", "coretech.storage.tenant.admin"); + + assertTrue(zms.dbService.isTrustRoleForTenant(mockFileConn, "sports", "coretech.storage.tenant.admin", + "coretech.storage.tenant.", "athenz")); + } + + @Test + public void testIsTrustRoleForTenantRoleTrustNoMatch() { + + Role role = new Role().setName(ZMSUtils.roleResourceName("sports", "coretech.storage.tenant.admin")) + .setTrust("athenz2"); + Mockito.doReturn(role).when(mockFileConn).getRole("sports", "coretech.storage.tenant.admin"); + + assertFalse(zms.dbService.isTrustRoleForTenant(mockFileConn, "sports", "coretech.storage.tenant.admin", + "coretech.storage.tenant.", "athenz")); + } + + @Test + public void testExecutePutPolicyCreate() { + + String domainName = "testreplacepolicycreatedomain"; + String policyName = "policy1"; + + TopLevelDomain dom = createTopLevelDomainObject(domainName, null, null, adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Policy policy = createPolicyObject(domainName, policyName); + zms.dbService.executePutPolicy(mockDomRsrcCtx, domainName, policyName, policy, + auditRef, "testReplacePolicyCreate"); + + Policy policyRes = zms.getPolicy(mockDomRsrcCtx, domainName, policyName); + assertNotNull(policyRes); + assertEquals(policyRes.getName(), domainName + ":policy." + policyName); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutPolicyExisting() { + + String domainName = "testreplacepolicycreatedomain"; + String policyName = "policy1"; + + TopLevelDomain dom = createTopLevelDomainObject(domainName, null, null, adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Policy policy = createPolicyObject(domainName, policyName); + zms.dbService.executePutPolicy(mockDomRsrcCtx, domainName, policyName, policy, + auditRef, "testExecutePutPolicyExisting"); + + Policy policyRes = zms.getPolicy(mockDomRsrcCtx, domainName, policyName); + assertNotNull(policyRes); + assertEquals(policyRes.getName(), domainName + ":policy." + policyName); + List asserts = policyRes.getAssertions(); + int origAssertCnt = (asserts == null) ? 0 : asserts.size(); + + // replace the existing policy with a modified version + + Assertion assertion = new Assertion(); + assertion.setAction("test").setEffect(AssertionEffect.ALLOW).setResource("tests"); + asserts = policy.getAssertions(); + asserts.add(assertion); + policy = policy.setAssertions(asserts); + + zms.dbService.executePutPolicy(mockDomRsrcCtx, domainName, policyName, policy, + auditRef, "testExecutePutPolicyExisting"); + + Policy policyRes2 = zms.getPolicy(mockDomRsrcCtx, domainName, policyName); + assertNotNull(policyRes2); + asserts = policyRes2.getAssertions(); + assertTrue(asserts.size() > origAssertCnt); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutPolicyMissingAuditRef() { + // create a new policy without an auditref + + String domain = "testreplacepolicymissingauditref"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, null, null, adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Policy policy = createPolicyObject(domain, "policy1"); + try { + zms.dbService.executePutPolicy(mockDomRsrcCtx, domain, "policy1", policy, + null, "testExecutePutPolicyMissingAuditRef"); + fail("requesterror not thrown by replacePolicy."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testExecuteDeleteEntity() { + + String domainName = "delentitydom1"; + String entityName = "entity1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Entity entity1 = createEntityObject(entityName); + zms.putEntity(mockDomRsrcCtx, domainName, entityName, auditRef, entity1); + + Entity entityRes = zms.getEntity(mockDomRsrcCtx, domainName, entityName); + assertNotNull(entityRes); + + zms.dbService.executeDeleteEntity(mockDomRsrcCtx, domainName, entityName, auditRef, "deleteEntity"); + + try { + entityRes = zms.getEntity(mockDomRsrcCtx, domainName, entityName); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecuteDeleteMembership() { + + String domainName = "mbrdeldom1"; + String roleName = "role1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject(domainName, roleName, null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, domainName, roleName, auditRef, role1); + zms.dbService.executeDeleteMembership(mockDomRsrcCtx, domainName, roleName, + "user.joe", auditRef, "deleteMembership"); + + Role role = zms.getRole(mockDomRsrcCtx, domainName, roleName, true, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 1); + + assertFalse(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecuteDeletePolicy() { + + String domainName = "policydeldom1"; + String policyName = "policy1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject(domainName, policyName); + zms.putPolicy(mockDomRsrcCtx, domainName, policyName, auditRef, policy1); + + Policy policyRes1 = zms.getPolicy(mockDomRsrcCtx, domainName, policyName); + assertNotNull(policyRes1); + + zms.dbService.executeDeletePolicy(mockDomRsrcCtx, domainName, policyName, + auditRef, "deletePolicy"); + + // we need to get an exception here + try { + zms.getPolicy(mockDomRsrcCtx, domainName, policyName); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecuteDeletePublicKeyEntry() { + + String domainName = "servicedelpubkeydom1"; + String serviceName = "service1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject(domainName, + serviceName, "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, domainName, serviceName, auditRef, service); + + zms.dbService.executeDeletePublicKeyEntry(mockDomRsrcCtx, domainName, serviceName, + "1", auditRef, "deletePublicKeyEntry"); + ServiceIdentity serviceRes = zms.getServiceIdentity(mockDomRsrcCtx, domainName, serviceName); + List keyList = serviceRes.getPublicKeys(); + boolean found = false; + for (PublicKeyEntry entry : keyList) { + if (entry.getId().equals("1")) { + found = true; + } + } + assertFalse(found); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecuteDeletePublicKeyEntryLastKeyNotAllowed() { + + String domainName = "servicedelpubkeydom1"; + String serviceName = "service1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject(domainName, + serviceName, "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, domainName, serviceName, auditRef, service); + + zms.dbService.executeDeletePublicKeyEntry(mockDomRsrcCtx, domainName, serviceName, + "1", auditRef, "deletePublicKeyEntry"); + + try { + zms.dbService.executeDeletePublicKeyEntry(mockDomRsrcCtx, domainName, serviceName, + "2", auditRef, "deletePublicKeyEntry"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecuteDeleteRole() { + + String domainName = "delroledom1"; + String roleName = "role1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject(domainName, roleName, null, "user.joe", + "user.jane"); + zms.putRole(mockDomRsrcCtx, domainName, roleName, auditRef, role1); + + RoleList roleList = zms.getRoleList(mockDomRsrcCtx, domainName, null, null); + assertNotNull(roleList); + + // our role count is +1 because of the admin role + assertEquals(roleList.getNames().size(), 2); + + zms.dbService.executeDeleteRole(mockDomRsrcCtx, domainName, roleName, auditRef, "deleteRole"); + + roleList = zms.getRoleList(mockDomRsrcCtx, domainName, null, null); + assertNotNull(roleList); + + // our role counti is +1 because of the admin role + assertEquals(roleList.getNames().size(), 1); + + assertFalse(roleList.getNames().contains(roleName)); + assertTrue(roleList.getNames().contains("admin")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecuteDeleteServiceIdentity() { + + String domainName = "servicedeldom1"; + String serviceName = "service1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service1 = createServiceObject(domainName, + serviceName, "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, domainName, serviceName, auditRef, service1); + + ServiceIdentity serviceRes1 = zms.getServiceIdentity(mockDomRsrcCtx, domainName, serviceName); + assertNotNull(serviceRes1); + + zms.dbService.executeDeleteServiceIdentity(mockDomRsrcCtx, domainName, serviceName, + auditRef, "deleteServiceIdentity"); + + // this should throw a not found exception + try { + zms.getServiceIdentity(mockDomRsrcCtx, domainName, serviceName); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecuteDeleteTenantRoles() { + + String tenantDomain = "deltenantrolesdom1"; + String providerDomain = "coretech"; + String providerService = "storage"; + + // create domain for tenant + + TopLevelDomain dom1 = createTopLevelDomainObject(tenantDomain, + "Test Tenant Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // create domain for provider + + TopLevelDomain domProv = createTopLevelDomainObject(providerDomain, + "Test Provider Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, domProv); + + // create service identity for providerDomain.providerService + + ServiceIdentity service = createServiceObject( + providerDomain, providerService, "http://localhost:8090/tableprovider", + "/usr/bin/java", "root", "users", "localhost"); + + zms.putServiceIdentity(mockDomRsrcCtx, providerDomain, providerService, auditRef, service); + + TenantRoles roles = zms.getTenantRoles(mockDomRsrcCtx, providerDomain, providerService, + tenantDomain); + assertNotNull(roles); + assertEquals(roles.getDomain(), providerDomain); + assertEquals(roles.getService(), providerService); + assertEquals(roles.getTenant(), tenantDomain); + assertEquals(roles.getRoles().size(), 0); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + TenantRoles tenantRoles = new TenantRoles().setDomain(providerDomain) + .setService(providerService).setTenant(tenantDomain) + .setRoles(roleActions); + + zms.putTenantRoles(mockDomRsrcCtx, providerDomain, providerService, tenantDomain, + auditRef, tenantRoles); + + RoleList roleList = zms.getRoleList(mockDomRsrcCtx, providerDomain, null, null); + assertNotNull(roleList); + + boolean readerFound = false; + boolean writerFound = false; + for (String roleName : roleList.getNames()) { + if (roleName.contains("reader")) { + readerFound = true; + } else if (roleName.contains("writer")) { + writerFound = true; + } + } + + assertTrue(readerFound); + assertTrue(writerFound); + + PolicyList policyList = zms.getPolicyList(mockDomRsrcCtx, providerDomain, null, null); + assertNotNull(policyList); + + readerFound = false; + writerFound = false; + for (String policy : policyList.getNames()) { + if (policy.contains("reader")) { + readerFound = true; + } else if (policy.contains("writer")) { + writerFound = true; + } + } + + assertTrue(readerFound); + assertTrue(writerFound); + + zms.dbService.executeDeleteTenantRoles(mockDomRsrcCtx, providerDomain, providerService, tenantDomain, + null, auditRef, "deleteTenantRoles"); + + roleList = zms.getRoleList(mockDomRsrcCtx, providerDomain, null, null); + assertNotNull(roleList); + + readerFound = false; + writerFound = false; + for (String roleName : roleList.getNames()) { + if (roleName.contains("reader")) { + readerFound = true; + } else if (roleName.contains("writer")) { + writerFound = true; + } + } + + assertFalse(readerFound); + assertFalse(writerFound); + + policyList = zms.getPolicyList(mockDomRsrcCtx, providerDomain, null, null); + assertNotNull(policyList); + + readerFound = false; + writerFound = false; + for (String policy : policyList.getNames()) { + if (policy.contains("reader")) { + readerFound = true; + } else if (policy.contains("writer")) { + writerFound = true; + } + } + + assertFalse(readerFound); + assertFalse(writerFound); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + } + + @Test + public void testExecutePutEntity() { + + String domainName = "createentitydom1"; + String entityName = "entity1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Entity entity1 = createEntityObject(entityName); + zms.dbService.executePutEntity(mockDomRsrcCtx, domainName, entityName, + entity1, auditRef, "putEntity"); + + Entity entity2 = zms.getEntity(mockDomRsrcCtx, domainName, entityName); + assertNotNull(entity2); + assertEquals(entity2.getName(), entityName); + + Struct value = entity2.getValue(); + assertEquals("Value1", value.getString("Key1")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutEntityUpdate() { + + String domainName = "createentitydom1-mod"; + String entityName = "entity1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Entity entity1 = createEntityObject(entityName); + zms.dbService.executePutEntity(mockDomRsrcCtx, domainName, entityName, + entity1, auditRef, "putEntity"); + + Struct value = new Struct(); + value.put("Key2", "Value2"); + entity1.setValue(value); + + zms.dbService.executePutEntity(mockDomRsrcCtx, domainName, entityName, + entity1, auditRef, "putEntity"); + + Entity entity2 = zms.getEntity(mockDomRsrcCtx, domainName, entityName); + assertNotNull(entity2); + assertEquals(entity2.getName(), entityName); + value = entity2.getValue(); + assertEquals("Value2", value.getString("Key2")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutDomainMeta() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MetaDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Domain resDom1 = zms.getDomain(mockDomRsrcCtx, "MetaDom1"); + assertNotNull(resDom1); + assertEquals("Test Domain1", resDom1.getDescription()); + assertEquals("testOrg", resDom1.getOrg()); + assertTrue(resDom1.getEnabled()); + assertFalse(resDom1.getAuditEnabled()); + + // update meta with values for account and product ids + + DomainMeta meta = new DomainMeta().setDescription("Test2 Domain").setOrg("NewOrg") + .setEnabled(true).setAuditEnabled(false).setAccount("12345").setYpmId(Integer.valueOf(1001)); + zms.dbService.executePutDomainMeta(mockDomRsrcCtx, "metadom1", meta, auditRef, "putDomainMeta"); + + Domain resDom2 = zms.getDomain(mockDomRsrcCtx, "MetaDom1"); + assertNotNull(resDom2); + assertEquals("Test2 Domain", resDom2.getDescription()); + assertEquals("NewOrg", resDom2.getOrg()); + assertTrue(resDom2.getEnabled()); + assertFalse(resDom2.getAuditEnabled()); + assertEquals(Integer.valueOf(1001), resDom2.getYpmId()); + assertEquals("12345", resDom2.getAccount()); + + // now update without account and product ids + + meta = new DomainMeta().setDescription("Test2 Domain-New").setOrg("NewOrg-New") + .setEnabled(true).setAuditEnabled(false); + zms.dbService.executePutDomainMeta(mockDomRsrcCtx, "metadom1", meta, auditRef, "putDomainMeta"); + + Domain resDom3 = zms.getDomain(mockDomRsrcCtx, "MetaDom1"); + assertNotNull(resDom3); + assertEquals("Test2 Domain-New", resDom3.getDescription()); + assertEquals("NewOrg-New", resDom3.getOrg()); + assertTrue(resDom3.getEnabled()); + assertFalse(resDom3.getAuditEnabled()); + assertEquals(Integer.valueOf(1001), resDom3.getYpmId()); + assertEquals("12345", resDom3.getAccount()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MetaDom1", auditRef); + } + + @Test + public void testExecutePutMembership() { + + String domainName = "mgradddom1"; + String roleName = "role1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("coretech", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + SubDomain subDom2 = createSubDomainObject("storage", "coretech", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "coretech", auditRef, subDom2); + + Role role1 = createRoleObject(domainName, roleName, null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, domainName, roleName, auditRef, role1); + + zms.dbService.executePutMembership(mockDomRsrcCtx, domainName, roleName, + "user.doe", auditRef, "putMembership"); + + zms.dbService.executePutMembership(mockDomRsrcCtx, domainName, roleName, + "coretech.storage", auditRef, "putMembership"); + + Role role = zms.getRole(mockDomRsrcCtx, domainName, roleName, false, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 4); + + assertTrue(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + assertTrue(members.contains("user.doe")); + assertTrue(members.contains("coretech.storage")); + + zms.deleteSubDomain(mockDomRsrcCtx, "coretech", "storage", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutMembershipTrustRole() { + + String domainName = "putmbrtrustrole"; + String roleName = "role1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject(domainName, roleName, "sys.auth", + null, null); + zms.putRole(mockDomRsrcCtx, domainName, roleName, auditRef, role1); + + try { + zms.dbService.executePutMembership(mockDomRsrcCtx, domainName, roleName, + "user.doe", auditRef, "putMembership"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutPolicy() { + + String domainName = "policyadddom1"; + String policyName = "policy1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject(domainName, policyName); + zms.dbService.executePutPolicy(mockDomRsrcCtx, domainName, policyName, + policy1, auditRef, "putPolicy"); + + Policy policyRes2 = zms.getPolicy(mockDomRsrcCtx, domainName, policyName); + assertNotNull(policyRes2); + assertEquals(policyRes2.getName(), domainName + ":policy." + policyName); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutPolicyInvalidDomain() { + + TopLevelDomain dom1 = createTopLevelDomainObject("PolicyAddDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject("PolicyAddDom1", "Policy1"); + + try { + zms.dbService.executePutPolicy(mockDomRsrcCtx, "PolicyAddDom1Invalid", "Policy1", + policy1, auditRef, "putPolicy"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "PolicyAddDom1", auditRef); + } + + @Test + public void testExecutePutPublicKeyEntry() { + + String domainName = "servicepubpubkeydom1"; + String serviceName = "service1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject(domainName, + serviceName, "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, domainName, serviceName, auditRef, service); + + PublicKeyEntry keyEntry = new PublicKeyEntry(); + keyEntry.setId("zone1"); + keyEntry.setKey(pubKeyK2); + + zms.dbService.executePutPublicKeyEntry(mockDomRsrcCtx, domainName, serviceName, + keyEntry, auditRef, "putPublicKeyEntry"); + + ServiceIdentity serviceRes = zms.getServiceIdentity(mockDomRsrcCtx, domainName, serviceName); + List keyList = serviceRes.getPublicKeys(); + boolean foundKey1 = false; + boolean foundKey2 = false; + boolean foundKeyZONE1 = false; + for (PublicKeyEntry entry : keyList) { + if (entry.getId().equals("1")) { + foundKey1 = true; + } else if (entry.getId().equals("2")) { + foundKey2 = true; + } else if (entry.getId().equals("zone1")) { + foundKeyZONE1 = true; + } + } + assertTrue(foundKey1); + assertTrue(foundKey2); + assertTrue(foundKeyZONE1); + + PublicKeyEntry entry = zms.getPublicKeyEntry(mockDomRsrcCtx, domainName, serviceName, "zone1"); + assertNotNull(entry); + assertEquals(entry.getId(), "zone1"); + assertEquals(entry.getKey(), pubKeyK2); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutRole() { + + String domainName = "executeputroledom1"; + String roleName = "role1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject(domainName, roleName, null, + "user.joe", "user.jane"); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, roleName, role1, auditRef, "putRole"); + + Role role3 = zms.getRole(mockDomRsrcCtx, domainName, roleName, false, false); + assertNotNull(role3); + assertEquals(role3.getName(), domainName + ":role." + roleName); + assertNull(role3.getTrust()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutRoleInvalidDomain() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ExecutePutRoleInvalidDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("ExecutePutRoleInvalidDom1", "Role1", null, + "user.joe", "user.jane"); + + try { + zms.dbService.executePutRole(mockDomRsrcCtx, "ExecutePutRoleInvalidDom2", + "Role1", role1, auditRef, "putRole"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ExecutePutRoleInvalidDom1", auditRef); + } + + @Test + public void testExecutePutServiceIdentity() { + + String domainName = "serviceadddom1"; + String serviceName = "service1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject(domainName, + serviceName, "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.dbService.executePutServiceIdentity(mockDomRsrcCtx, domainName, serviceName, + service, auditRef, "putServiceIdentity"); + + ServiceIdentity serviceRes2 = zms.getServiceIdentity(mockDomRsrcCtx, domainName, + serviceName); + assertNotNull(serviceRes2); + assertEquals(serviceRes2.getName(), domainName + "." + serviceName); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutServiceIdentityRetryException() { + + String domainName = "serviceadddom1"; + String serviceName = "service1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject(domainName, + serviceName, "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + Domain domain = new Domain().setAuditEnabled(false); + Mockito.when(mockObjStore.getConnection(false)).thenReturn(mockFileConn); + Mockito.when(mockFileConn.getDomain(domainName)).thenReturn(domain); + Mockito.when(mockFileConn.insertServiceIdentity(domainName, service)) + .thenThrow(new ResourceException(ResourceException.CONFLICT, "conflict")); + + ObjectStore saveStore = zms.dbService.store; + zms.dbService.store = mockObjStore; + int saveRetryCount = zms.dbService.defaultRetryCount; + zms.dbService.defaultRetryCount = 2; + + try { + zms.dbService.executePutServiceIdentity(mockDomRsrcCtx, domainName, serviceName, + service, auditRef, "putServiceIdentity"); + fail(); + } catch (ResourceException ex) { + assertEquals(ResourceException.CONFLICT, ex.getCode()); + } + + zms.dbService.defaultRetryCount = saveRetryCount; + zms.dbService.store = saveStore; + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutServiceIdentityModifyHost() { + + String domainName = "serviceadddom1-modhost"; + String serviceName = "service1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject(domainName, + serviceName, "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.dbService.executePutServiceIdentity(mockDomRsrcCtx, domainName, serviceName, + service, auditRef, "putServiceIdentity"); + + service.getHosts().add("host2"); + service.getHosts().add("host3"); + service.getHosts().remove("host1"); + + zms.dbService.executePutServiceIdentity(mockDomRsrcCtx, domainName, serviceName, + service, auditRef, "putServiceIdentity"); + + ServiceIdentity serviceRes2 = zms.getServiceIdentity(mockDomRsrcCtx, domainName, + serviceName); + assertNotNull(serviceRes2); + assertEquals(serviceRes2.getName(), domainName + "." + serviceName); + assertEquals(2, service.getHosts().size()); + assertTrue(service.getHosts().contains("host2")); + assertTrue(service.getHosts().contains("host3")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testExecutePutServiceIdentityInvalidDomain() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceAddDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServiceAddDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + try { + zms.dbService.executePutServiceIdentity(mockDomRsrcCtx, "ServiceAddDom1Invalid", + "Service1", service, auditRef, "putServiceIdentity"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceAddDom1", auditRef); + } + + @Test + public void testExecutePutTenantRoles() throws Exception { + + String tenantDomain = "tenantadminpolicy"; + String providerDomain = "coretech"; + String providerService = "storage"; + + // create domain for tenant + + TopLevelDomain dom1 = createTopLevelDomainObject(tenantDomain, + "Test Tenant Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // create domain for provider + + TopLevelDomain domProv = createTopLevelDomainObject(providerDomain, + "Test Provider Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, domProv); + + // create service identity for providerDomain.providerService + + ServiceIdentity service = createServiceObject( + providerDomain, providerService, "http://localhost:8090/tableprovider", + "/usr/bin/java", "root", "users", "localhost"); + + zms.putServiceIdentity(mockDomRsrcCtx, providerDomain, providerService, auditRef, service); + + Tenancy tenant = new Tenancy(); + tenant.setDomain(tenantDomain); + tenant.setService("coretech.storage"); + + ProviderMockClient.setReturnTenantRoles(true); + zms.putTenancy(mockDomRsrcCtx, tenantDomain, "coretech.storage", auditRef, tenant); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + + zms.dbService.executePutTenantRoles(mockDomRsrcCtx, providerDomain, providerService, + tenantDomain, null, roleActions, auditRef, "putTenantRoles"); + + Tenancy tenant1 = zms.getTenancy(mockDomRsrcCtx, tenantDomain, "coretech.storage"); + assertNotNull(tenant1); + + zms.deleteTenancy(mockDomRsrcCtx, tenantDomain, "coretech.storage", auditRef); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + } + + void verifyPolicies(String domainName) { + + List names = zms.dbService.listPolicies(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + // this should be our own policy that we created previously + Policy policy = zms.dbService.getPolicy(domainName, "vip_admin"); + assertEquals(domainName + ":policy.vip_admin", policy.getName()); + + // The updated policy will have two assertions, one from the original, and the other from template application. + // The original assertion is { role: "solutiontemplate-withpolicy:role.role1", action: "*", effect: "ALLOW", resource: "*"} + // Newly added one is { resource: "solutiontemplate-withpolicy:vip*", role: "solutiontemplate-withpolicy:role.vip_admin", action: "*"} + assertEquals(2, policy.getAssertions().size()); + Assertion assertion = policy.getAssertions().get(0); // this is the original assertion + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.role1", assertion.getRole()); + assertEquals("solutiontemplate-withpolicy:*", assertion.getResource()); + + Assertion assertionAdded = policy.getAssertions().get(1); // this is the added assertion + assertEquals("*", assertionAdded.getAction()); + assertEquals(domainName + ":role.vip_admin", assertionAdded.getRole()); + assertEquals("solutiontemplate-withpolicy:vip*", assertionAdded.getResource()); + } + + @Test + public void testApplySolutionTemplateDomainExistingPolicies() { + + String caller = "testApplySolutionTemplateDomainExistingPolicies"; + String domainName = "solutiontemplate-withpolicy"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // we are going to create one of the policies that's also in + // the template - this should not change + + Policy policy1 = createPolicyObject(domainName, "vip_admin"); + zms.putPolicy(mockDomRsrcCtx, domainName, "vip_admin", auditRef, policy1); + + // apply the template + + List templates = new ArrayList<>(); + templates.add("vipng"); + zms.dbService.executePutDomainTemplate(mockDomRsrcCtx, domainName, templates, auditRef, caller); + + DomainTemplateList domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertEquals(1, domainTemplateList.getTemplateNames().size()); + + // verify that our role collection includes the expected roles + + List names = zms.dbService.listRoles(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + Role role = zms.dbService.getRole(domainName, "vip_admin", false, false); + assertEquals(domainName + ":role.vip_admin", role.getName()); + assertNull(role.getTrust()); + assertNull(role.getMembers()); + + role = zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false); + assertEquals(domainName + ":role.sys_network_super_vip_admin", role.getName()); + assertEquals("sys.network", role.getTrust()); + + // verify that our policy collections includes the policies defined in the template + + verifyPolicies(domainName); + + // Try applying the template again. This time, there should be no changes. + + zms.dbService.executePutDomainTemplate(mockDomRsrcCtx, domainName, templates, auditRef, caller); + + verifyPolicies(domainName); + + // the rest should be identical what's in the template + + Policy policy = zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin"); + assertEquals(domainName + ":policy.sys_network_super_vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + Assertion assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.sys_network_super_vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + // remove the vipng template + + zms.dbService.executeDeleteDomainTemplate(mockDomRsrcCtx, domainName, "vipng", + auditRef, caller); + + assertNull(zms.dbService.getRole(domainName, "vip_admin", false, false)); + assertNull(zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false)); + assertNull(zms.dbService.getPolicy(domainName, "vip_admin")); + assertNull(zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin")); + assertNotNull(zms.dbService.getRole(domainName, "admin", false, false)); + + domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertTrue(domainTemplateList.getTemplateNames().isEmpty()); + + // remove vipng again to ensure same result + + zms.dbService.executeDeleteDomainTemplate(mockDomRsrcCtx, domainName, "vipng", + auditRef, caller); + + assertNull(zms.dbService.getRole(domainName, "vip_admin", false, false)); + assertNull(zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false)); + assertNull(zms.dbService.getPolicy(domainName, "vip_admin")); + assertNull(zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin")); + assertNotNull(zms.dbService.getRole(domainName, "admin", false, false)); + + domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertTrue(domainTemplateList.getTemplateNames().isEmpty()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testApplySolutionTemplateMultipleTemplates() { + + String caller = "testApplySolutionTemplateMultipleTemplates"; + String domainName = "solutiontemplate-multi"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // apply the template + + List templates = new ArrayList<>(); + templates.add("vipng"); + zms.dbService.executePutDomainTemplate(mockDomRsrcCtx, domainName, templates, auditRef, caller); + + DomainTemplateList domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertEquals(1, domainTemplateList.getTemplateNames().size()); + + // verify that our role collection includes the roles defined in template + + List names = zms.dbService.listRoles(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + Role role = zms.dbService.getRole(domainName, "vip_admin", false, false); + assertEquals(domainName + ":role.vip_admin", role.getName()); + assertNull(role.getTrust()); + assertNull(role.getMembers()); + + role = zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false); + assertEquals(domainName + ":role.sys_network_super_vip_admin", role.getName()); + assertEquals("sys.network", role.getTrust()); + + // verify that our policy collections includes the policies defined in the template + + names = zms.dbService.listPolicies(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + Policy policy = zms.dbService.getPolicy(domainName, "vip_admin"); + assertEquals(domainName + ":policy.vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + Assertion assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + policy = zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin"); + assertEquals(domainName + ":policy.sys_network_super_vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.sys_network_super_vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + // add another template + templates = new ArrayList<>(); + templates.add("platforms"); + zms.dbService.executePutDomainTemplate(mockDomRsrcCtx, domainName, templates, auditRef, caller); + + domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertEquals(2, domainTemplateList.getTemplateNames().size()); + + names = zms.dbService.listRoles(domainName); + assertEquals(4, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("platforms_deployer")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + names = zms.dbService.listPolicies(domainName); + assertEquals(4, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + assertTrue(names.contains("platforms_deploy")); + + // remove the vipng template + + zms.dbService.executeDeleteDomainTemplate(mockDomRsrcCtx, domainName, "vipng", + auditRef, caller); + + domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertEquals(1, domainTemplateList.getTemplateNames().size()); + + names = zms.dbService.listRoles(domainName); + assertEquals(2, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("platforms_deployer")); + + names = zms.dbService.listPolicies(domainName); + assertEquals(2, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("platforms_deploy")); + + // remove the platforms template + + zms.dbService.executeDeleteDomainTemplate(mockDomRsrcCtx, domainName, "platforms", + auditRef, caller); + + domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertEquals(0, domainTemplateList.getTemplateNames().size()); + + names = zms.dbService.listRoles(domainName); + assertEquals(1, names.size()); + assertTrue(names.contains("admin")); + + names = zms.dbService.listPolicies(domainName); + assertEquals(1, names.size()); + assertTrue(names.contains("admin")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testApplySolutionTemplateEmptyDomain() { + + String domainName = "solutiontemplate-ok"; + String caller = "testApplySolutionTemplateDomainExistingPolicies"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // apply the template + + List templates = new ArrayList<>(); + templates.add("vipng"); + zms.dbService.executePutDomainTemplate(mockDomRsrcCtx, domainName, templates, auditRef, caller); + + DomainTemplateList domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertEquals(1, domainTemplateList.getTemplateNames().size()); + + // verify that our role collection includes the expected roles + + List names = zms.dbService.listRoles(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + Role role = zms.dbService.getRole(domainName, "vip_admin", false, false); + assertEquals(domainName + ":role.vip_admin", role.getName()); + assertNull(role.getTrust()); + assertNull(role.getMembers()); + + role = zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false); + assertEquals(domainName + ":role.sys_network_super_vip_admin", role.getName()); + assertEquals("sys.network", role.getTrust()); + + // verify that our policy collections includes the policies defined in the template + + names = zms.dbService.listPolicies(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + // Try applying the template again. This time, there should be no changes. + + zms.dbService.executePutDomainTemplate(mockDomRsrcCtx, domainName, templates, auditRef, caller); + + names = zms.dbService.listPolicies(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + // the rest should be identical what's in the template + + Policy policy = zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin"); + assertEquals(domainName + ":policy.sys_network_super_vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + Assertion assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.sys_network_super_vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + // remove the vipng template + + zms.dbService.executeDeleteDomainTemplate(mockDomRsrcCtx, domainName, "vipng", + auditRef, caller); + + assertNull(zms.dbService.getRole(domainName, "vip_admin", false, false)); + assertNull(zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false)); + assertNull(zms.dbService.getPolicy(domainName, "vip_admin")); + assertNull(zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin")); + assertNotNull(zms.dbService.getRole(domainName, "admin", false, false)); + + domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertTrue(domainTemplateList.getTemplateNames().isEmpty()); + + // remove vipng again to ensure same result + + zms.dbService.executeDeleteDomainTemplate(mockDomRsrcCtx, domainName, "vipng", + auditRef, caller); + + assertNull(zms.dbService.getRole(domainName, "vip_admin", false, false)); + assertNull(zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false)); + assertNull(zms.dbService.getPolicy(domainName, "vip_admin")); + assertNull(zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin")); + assertNotNull(zms.dbService.getRole(domainName, "admin", false, false)); + + domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertTrue(domainTemplateList.getTemplateNames().isEmpty()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testApplySolutionTemplateMultipleTimes() { + + String caller = "testApplySolutionTemplateMultipleTimes"; + String domainName = "solutiontemplate-multiple"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // apply the template + + List templates = new ArrayList<>(); + templates.add("vipng"); + zms.dbService.executePutDomainTemplate(mockDomRsrcCtx, domainName, templates, auditRef, caller); + + // apply the template again - nothing should change + + zms.dbService.executePutDomainTemplate(mockDomRsrcCtx, domainName, templates, auditRef, caller); + + DomainTemplateList domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertEquals(1, domainTemplateList.getTemplateNames().size()); + + // verify that our role collection includes the expected roles + + List names = zms.dbService.listRoles(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + Role role = zms.dbService.getRole(domainName, "vip_admin", false, false); + assertEquals(domainName + ":role.vip_admin", role.getName()); + assertNull(role.getTrust()); + assertNull(role.getMembers()); + + role = zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false); + assertEquals(domainName + ":role.sys_network_super_vip_admin", role.getName()); + assertEquals("sys.network", role.getTrust()); + + // verify that our policy collections includes the policies defined in the template + + names = zms.dbService.listPolicies(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + Policy policy = zms.dbService.getPolicy(domainName, "vip_admin"); + assertEquals(domainName + ":policy.vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + Assertion assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + policy = zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin"); + assertEquals(domainName + ":policy.sys_network_super_vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.sys_network_super_vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + // remove the vipng template + + zms.dbService.executeDeleteDomainTemplate(mockDomRsrcCtx, domainName, "vipng", + auditRef, caller); + + assertNull(zms.dbService.getRole(domainName, "vip_admin", false, false)); + assertNull(zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false)); + assertNull(zms.dbService.getPolicy(domainName, "vip_admin")); + assertNull(zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin")); + assertNotNull(zms.dbService.getRole(domainName, "admin", false, false)); + + domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertTrue(domainTemplateList.getTemplateNames().isEmpty()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testApplySolutionTemplateExistingRoles() { + + String caller = "testApplySolutionTemplateExistingRoles"; + String domainName = "solutiontemplate-withrole"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // we are going to create one of the roles that's also in + // the template - this should not change + + Role role1 = createRoleObject(domainName, "vip_admin", null, "user.joe", + "user.jane"); + zms.putRole(mockDomRsrcCtx, domainName, "vip_admin", auditRef, role1); + + // apply the template + + List templates = new ArrayList<>(); + templates.add("vipng"); + zms.dbService.executePutDomainTemplate(mockDomRsrcCtx, domainName, templates, auditRef, caller); + + // apply the template again - nothing should change + + zms.dbService.executePutDomainTemplate(mockDomRsrcCtx, domainName, templates, auditRef, caller); + + DomainTemplateList domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertEquals(1, domainTemplateList.getTemplateNames().size()); + + // verify that our role collection includes the expected roles + + List names = zms.dbService.listRoles(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + // this should be our own role that we created previously + + Role role = zms.dbService.getRole(domainName, "vip_admin", false, false); + assertEquals(domainName + ":role.vip_admin", role.getName()); + assertNull(role.getTrust()); + assertEquals(2, role.getMembers().size()); + assertTrue(role.getMembers().contains("user.joe")); + assertTrue(role.getMembers().contains("user.jane")); + + // the rest should be whatever we had in the template + + role = zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false); + assertEquals(domainName + ":role.sys_network_super_vip_admin", role.getName()); + assertEquals("sys.network", role.getTrust()); + + // the rest should be whatever we had in the template + + names = zms.dbService.listPolicies(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + Policy policy = zms.dbService.getPolicy(domainName, "vip_admin"); + assertEquals(domainName + ":policy.vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + Assertion assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + policy = zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin"); + assertEquals(domainName + ":policy.sys_network_super_vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.sys_network_super_vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + // remove the vipng template + + zms.dbService.executeDeleteDomainTemplate(mockDomRsrcCtx, domainName, "vipng", + auditRef, caller); + + assertNull(zms.dbService.getRole(domainName, "vip_admin", false, false)); + assertNull(zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false)); + assertNull(zms.dbService.getPolicy(domainName, "vip_admin")); + assertNull(zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin")); + assertNotNull(zms.dbService.getRole(domainName, "admin", false, false)); + + domainTemplateList = zms.dbService.listDomainTemplates(domainName); + assertTrue(domainTemplateList.getTemplateNames().isEmpty()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testSetupTenantAdminPolicy() { + + String caller = "testSetupTenantAdminPolicy"; + String tenantDomain = "tenantadminpolicy"; + String providerDomain = "coretech"; + String providerService = "storage"; + + // create domain for tenant + + TopLevelDomain dom1 = createTopLevelDomainObject(tenantDomain, + "Test Tenant Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // create domain for provider + + TopLevelDomain domProv = createTopLevelDomainObject(providerDomain, + "Test Provider Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, domProv); + + // create service identity for providerDomain.providerService + + ServiceIdentity service = createServiceObject( + providerDomain, providerService, "http://localhost:8090/tableprovider", + "/usr/bin/java", "root", "users", "localhost"); + + zms.putServiceIdentity(mockDomRsrcCtx, providerDomain, providerService, auditRef, service); + + // let's create the tenant admin policy + + zms.dbService.setupTenantAdminPolicy(mockDomRsrcCtx, tenantDomain, providerDomain, + providerService, auditRef, caller); + + // the admin policy must be called + + String policyName = "tenancy.coretech.storage.admin"; + Policy policy = zms.dbService.getPolicy(tenantDomain, policyName); + assertNotNull(policy); + + List assertList = policy.getAssertions(); + assertNotNull(assertList); + assertEquals(3, assertList.size()); + boolean domainAdminRoleCheck = false; + boolean tenantAdminRoleCheck = false; + boolean tenantUpdateCheck = false; + for (Assertion obj : assertList) { + assertEquals(AssertionEffect.ALLOW, obj.getEffect()); + if (obj.getRole().equals("tenantadminpolicy:role.admin")) { + assertEquals(obj.getResource(), "coretech:role.storage.tenant.tenantadminpolicy.admin"); + assertEquals(obj.getAction(), "assume_role"); + domainAdminRoleCheck = true; + } else if (obj.getRole().equals("tenantadminpolicy:role.tenancy.coretech.storage.admin")) { + if (obj.getAction().equals("assume_role")) { + assertEquals(obj.getResource(), "coretech:role.storage.tenant.tenantadminpolicy.admin"); + tenantAdminRoleCheck = true; + } else if (obj.getAction().equals("update")) { + assertEquals("tenantadminpolicy:tenancy.coretech.storage", obj.getResource()); + tenantUpdateCheck = true; + } + } + } + assertTrue(domainAdminRoleCheck); + assertTrue(tenantAdminRoleCheck); + assertTrue(tenantUpdateCheck); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + } + + @Test + public void testInvalidDBServiceConfig() { + + System.setProperty(ZMSConsts.ZMS_PROP_CONFLICT_RETRY_COUNT, "-100"); + System.setProperty(ZMSConsts.ZMS_PROP_CONFLICT_RETRY_SLEEP_TIME, "-1000"); + + DBService dbService = new DBService(null, null, "user"); + assertEquals(120, dbService.defaultRetryCount); + assertEquals(250, dbService.retrySleepTime); + + System.clearProperty(ZMSConsts.ZMS_PROP_CONFLICT_RETRY_COUNT); + System.clearProperty(ZMSConsts.ZMS_PROP_CONFLICT_RETRY_SLEEP_TIME); + } + + @Test + public void testShouldRetryOperation() { + + FileObjectStore store = new FileObjectStore(new File(".")); + DBService dbService = new DBService(store, null, "user"); + + // regardless of exception, count of 0 or 1 returns false + + assertFalse(dbService.shouldRetryOperation(null, 0)); + assertFalse(dbService.shouldRetryOperation(null, 1)); + + // conflict returns true + + ResourceException exc = new ResourceException(ResourceException.CONFLICT, "unit-test"); + assertTrue(dbService.shouldRetryOperation(exc, 2)); + + // gone - read/only mode returns true + + exc = new ResourceException(ResourceException.GONE, "unit-test"); + assertTrue(dbService.shouldRetryOperation(exc, 2)); + + // all others return false + + exc = new ResourceException(ResourceException.BAD_REQUEST, "unit-test"); + assertFalse(dbService.shouldRetryOperation(exc, 2)); + + exc = new ResourceException(ResourceException.FORBIDDEN, "unit-test"); + assertFalse(dbService.shouldRetryOperation(exc, 2)); + + exc = new ResourceException(ResourceException.INTERNAL_SERVER_ERROR, "unit-test"); + assertFalse(dbService.shouldRetryOperation(exc, 2)); + + exc = new ResourceException(ResourceException.NOT_FOUND, "unit-test"); + assertFalse(dbService.shouldRetryOperation(exc, 2)); + } + + @Test + public void testLookupDomainByAccount() { + + String domainName = "lookupdomainaccount"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + dom1.setAccount("1234"); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + DomainList list = zms.dbService.lookupDomainByAccount("1234"); + assertNotNull(list.getNames()); + assertEquals(list.getNames().size(), 1); + assertEquals(list.getNames().get(0), domainName); + + list = zms.dbService.lookupDomainById("1234", 0); + assertNotNull(list.getNames()); + assertEquals(list.getNames().size(), 1); + assertEquals(list.getNames().get(0), domainName); + + list = zms.dbService.lookupDomainByAccount("1235"); + assertNull(list.getNames()); + + list = zms.dbService.lookupDomainById("1235", 0); + assertNull(list.getNames()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testLookupDomainByProductId() { + + String domainName = "lookupdomainbyproductid"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + dom1.setYpmId(Integer.valueOf(101)); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + DomainList list = zms.dbService.lookupDomainByProductId(101); + assertNotNull(list.getNames()); + assertEquals(list.getNames().size(), 1); + assertEquals(list.getNames().get(0), domainName); + + list = zms.dbService.lookupDomainById(null, 101); + assertNotNull(list.getNames()); + assertEquals(list.getNames().size(), 1); + assertEquals(list.getNames().get(0), domainName); + + list = zms.dbService.lookupDomainByProductId(102); + assertNull(list.getNames()); + + list = zms.dbService.lookupDomainById(null, 102); + assertNull(list.getNames()); + + list = zms.dbService.lookupDomainByProductId(0); + assertNull(list.getNames()); + + list = zms.dbService.lookupDomainById(null, 0); + assertNull(list.getNames()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testLookupDomainByRole() { + + String domainName = "lookupdomainrole"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + dom1.setYpmId(Integer.valueOf(199)); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + DomainList list = zms.dbService.lookupDomainByRole(adminUser, "admin"); + assertNotNull(list.getNames()); + assertTrue(list.getNames().contains(domainName)); + + List doms = zms.dbService.listDomains(null, 0); + + // all domains have admin role + + list = zms.dbService.lookupDomainByRole(null, "admin"); + assertNotNull(list.getNames()); + assertEquals(list.getNames().size(), doms.size()); + + // null role/member gives us the full set + + list = zms.dbService.lookupDomainByRole(null, null); + assertNotNull(list.getNames()); + assertEquals(list.getNames().size(), doms.size()); + + // unknown role and member in the system + + list = zms.dbService.lookupDomainByRole(adminUser, "unknown_role"); + assertEquals(list.getNames().size(), 0); + + list = zms.dbService.lookupDomainByRole("unkwown-user", "admin"); + assertEquals(list.getNames().size(), 0); + + // lets add a role for joe and jane + + Role role1 = createRoleObject(domainName, "list-role", null, + "user.joe", "user.jane"); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "list-role", + role1, auditRef, "putRole"); + + list = zms.dbService.lookupDomainByRole("user.joe", "list-role"); + assertNotNull(list.getNames()); + assertEquals(list.getNames().size(), 1); + assertEquals(list.getNames().get(0), domainName); + + list = zms.dbService.lookupDomainByRole(adminUser, "list-role"); + assertEquals(list.getNames().size(), 0); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetPrincipalYrn() { + + Principal principal = SimplePrincipal.create("user", "user1", "creds", null); + ZMSImpl.RsrcCtxWrapper rsrcCtx = Mockito.mock(ZMSImpl.RsrcCtxWrapper.class); + Mockito.when(rsrcCtx.principal()).thenReturn(principal); + assertEquals(zms.dbService.getPrincipalYrn(rsrcCtx), "user.user1"); + + assertNull(zms.dbService.getPrincipalYrn(null)); + + ZMSImpl.RsrcCtxWrapper rsrcCtx2 = Mockito.mock(ZMSImpl.RsrcCtxWrapper.class); + Mockito.when(rsrcCtx2.principal()).thenReturn(null); + assertNull(zms.dbService.getPrincipalYrn(rsrcCtx2)); + } + + @Test + public void testAuditLogPublicKeyEntry() { + StringBuilder auditDetails = new StringBuilder(); + assertFalse(zms.dbService.auditLogPublicKeyEntry(auditDetails, "keyId", true)); + assertEquals("{id: \"keyId\"}", auditDetails.toString()); + + auditDetails.setLength(0); + assertFalse(zms.dbService.auditLogPublicKeyEntry(auditDetails, "keyId", false)); + assertEquals(",{id: \"keyId\"}", auditDetails.toString()); + } + + @Test + public void testApplySolutionTemplateNullTemplat() { + StringBuilder auditDetails = new StringBuilder(); + assertTrue(zms.dbService.applySolutionTemplate(null, null, "template1", null, true, null, null, auditDetails)); + assertEquals("{name: \"template1\"}", auditDetails.toString()); + } + + @Test + public void testIsTrustRole() { + + // null role + assertFalse(zms.dbService.isTrustRole(null)); + + // null trust + Role role = new Role().setName("domain1:role.role1").setTrust(null); + assertFalse(zms.dbService.isTrustRole(role)); + + // empty trust + role = new Role().setName("domain1:role.role1").setTrust(""); + assertFalse(zms.dbService.isTrustRole(role)); + + // with trust + role = new Role().setName("domain1:role.role1").setTrust("domain2"); + assertTrue(zms.dbService.isTrustRole(role)); + } + + @Test + public void testGetDelegatedRoleNoExpand() { + + String domainName = "rolenoexpand"; + String roleName = "role1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject(domainName, roleName, "sys.auth", + null, null); + zms.putRole(mockDomRsrcCtx, domainName, roleName, auditRef, role1); + + Role role = zms.dbService.getRole(domainName, roleName, false, false); + assertNotNull(role); + assertEquals(role.getTrust(), "sys.auth"); + assertNull(role.getMembers()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetDelegatedRoleExpand() { + + String domainName1 = "role-expand1"; + String domainName2 = "role-expand2"; + + String roleName = "role1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName1, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject(domainName2, + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + Role role1 = createRoleObject(domainName1, roleName, domainName2, + null, null); + zms.putRole(mockDomRsrcCtx, domainName1, roleName, auditRef, role1); + + Role role2a = createRoleObject(domainName2, "role2a", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, domainName2, "role2a", auditRef, role2a); + + Role role2b = createRoleObject(domainName2, "role2b", null, + "user.joe", "user.doe"); + zms.putRole(mockDomRsrcCtx, domainName2, "role2b", auditRef, role2b); + + Policy policy = createPolicyObject(domainName2, "policy", + domainName2 + ":role.role2a", false, "assume_role", domainName1 + ":role." + roleName, + AssertionEffect.ALLOW); + + Assertion assertion = new Assertion(); + assertion.setAction("assume_role"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role." + roleName); + assertion.setRole(domainName2 + ":role.role2b"); + policy.getAssertions().add(assertion); + zms.putPolicy(mockDomRsrcCtx, domainName2, "policy", auditRef, policy); + + Role role = zms.dbService.getRole(domainName1, roleName, false, true); + assertNotNull(role); + assertEquals(role.getTrust(), domainName2); + List members = role.getMembers(); + assertEquals(3, members.size()); + + assertTrue(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + assertTrue(members.contains("user.doe")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName1, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName2, auditRef); + } + + @Test + public void testGetDelegatedRoleMembersInvalidDomain() { + + assertNull(zms.dbService.getDelegatedRoleMembers("dom1", "dom1", "role1")); + assertNull(zms.dbService.getDelegatedRoleMembers("dom1", "invalid-domain", "role1")); + } + + @Test + public void testGetDelegatedRoleMembers() { + + String domainName1 = "role-expand1"; + String domainName2 = "role-expand2"; + + String roleName = "role1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName1, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject(domainName2, + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + Role role1 = createRoleObject(domainName1, roleName, domainName2, + null, null); + zms.putRole(mockDomRsrcCtx, domainName1, roleName, auditRef, role1); + + Role role2a = createRoleObject(domainName2, "role2a", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, domainName2, "role2a", auditRef, role2a); + + Role role2b = createRoleObject(domainName2, "role2b", null, + "user.joe", "user.doe"); + zms.putRole(mockDomRsrcCtx, domainName2, "role2b", auditRef, role2b); + + Role role2c = createRoleObject(domainName2, "role2c", "sys.auth", + null, null); + zms.putRole(mockDomRsrcCtx, domainName2, "role2c", auditRef, role2c); + + Role role2d = createRoleObject(domainName2, "role2d", null, + "user.user1", "user.user2"); + zms.putRole(mockDomRsrcCtx, domainName2, "role2d", auditRef, role2d); + + Role role2e = createRoleObject(domainName2, "role2e", null, + null, null); + zms.putRole(mockDomRsrcCtx, domainName2, "role2e", auditRef, role2e); + + Policy policy = createPolicyObject(domainName2, "policy", + domainName2 + ":role.role2a", false, "assume_role", domainName1 + ":role." + roleName, + AssertionEffect.ALLOW); + + Assertion assertion = new Assertion(); + assertion.setAction("assume_role"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role." + roleName); + assertion.setRole(domainName2 + ":role.role2b"); + policy.getAssertions().add(assertion); + zms.putPolicy(mockDomRsrcCtx, domainName2, "policy", auditRef, policy); + + policy = new Policy().setName(domainName2 + ":policy.policy2"); + zms.dbService.executePutPolicy(mockDomRsrcCtx, domainName2, "policy2", policy, auditRef, "putPolicy"); + + List members = zms.dbService.getDelegatedRoleMembers(domainName1, domainName2, roleName); + assertEquals(3, members.size()); + + assertTrue(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + assertTrue(members.contains("user.doe")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName1, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName2, auditRef); + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/MockConnectionFactory.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/MockConnectionFactory.java new file mode 100644 index 00000000000..7c76123046a --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/MockConnectionFactory.java @@ -0,0 +1,30 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.apache.commons.dbcp2.ConnectionFactory; + +public class MockConnectionFactory implements ConnectionFactory { + + @Override + public Connection createConnection() throws SQLException { + return null; + } + +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/MockHttpServletRequest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/MockHttpServletRequest.java new file mode 100644 index 00000000000..d170f20e5ab --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/MockHttpServletRequest.java @@ -0,0 +1,462 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; + +public class MockHttpServletRequest implements HttpServletRequest { + + Map headers = new HashMap<>(); + + public MockHttpServletRequest() { + } + + public void addHeader(String name, String value) { + headers.put(name, value); + } + + @Override + public Object getAttribute(String name) { + // Auto-generated method stub + return null; + } + + @Override + public Enumeration getAttributeNames() { + // Auto-generated method stub + return null; + } + + @Override + public String getCharacterEncoding() { + // Auto-generated method stub + return null; + } + + @Override + public void setCharacterEncoding(String env) throws UnsupportedEncodingException { + // Auto-generated method stub + } + + @Override + public int getContentLength() { + // Auto-generated method stub + return 0; + } + + @Override + public long getContentLengthLong() { + // Auto-generated method stub + return 0; + } + + @Override + public String getContentType() { + // Auto-generated method stub + return null; + } + + @Override + public ServletInputStream getInputStream() throws IOException { + // Auto-generated method stub + return null; + } + + @Override + public String getParameter(String name) { + // Auto-generated method stub + return null; + } + + @Override + public Enumeration getParameterNames() { + // Auto-generated method stub + return null; + } + + @Override + public String[] getParameterValues(String name) { + // Auto-generated method stub + return null; + } + + @Override + public Map getParameterMap() { + // Auto-generated method stub + return null; + } + + @Override + public String getProtocol() { + // Auto-generated method stub + return null; + } + + @Override + public String getScheme() { + // Auto-generated method stub + return null; + } + + @Override + public String getServerName() { + // Auto-generated method stub + return null; + } + + @Override + public int getServerPort() { + // Auto-generated method stub + return 0; + } + + @Override + public BufferedReader getReader() throws IOException { + // Auto-generated method stub + return null; + } + + @Override + public String getRemoteAddr() { + // Auto-generated method stub + return null; + } + + @Override + public String getRemoteHost() { + // Auto-generated method stub + return null; + } + + @Override + public void setAttribute(String name, Object o) { + // Auto-generated method stub + } + + @Override + public void removeAttribute(String name) { + // Auto-generated method stub + } + + @Override + public Locale getLocale() { + // Auto-generated method stub + return null; + } + + @Override + public Enumeration getLocales() { + // Auto-generated method stub + return null; + } + + @Override + public boolean isSecure() { + // Auto-generated method stub + return false; + } + + @Override + public RequestDispatcher getRequestDispatcher(String path) { + // Auto-generated method stub + return null; + } + + @Override + public String getRealPath(String path) { + // Auto-generated method stub + return null; + } + + @Override + public int getRemotePort() { + // Auto-generated method stub + return 0; + } + + @Override + public String getLocalName() { + // Auto-generated method stub + return null; + } + + @Override + public String getLocalAddr() { + // Auto-generated method stub + return null; + } + + @Override + public int getLocalPort() { + // Auto-generated method stub + return 0; + } + + @Override + public ServletContext getServletContext() { + // Auto-generated method stub + return null; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + // Auto-generated method stub + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) + throws IllegalStateException { + // Auto-generated method stub + return null; + } + + @Override + public boolean isAsyncStarted() { + // Auto-generated method stub + return false; + } + + @Override + public boolean isAsyncSupported() { + // Auto-generated method stub + return false; + } + + @Override + public AsyncContext getAsyncContext() { + // Auto-generated method stub + return null; + } + + @Override + public DispatcherType getDispatcherType() { + // Auto-generated method stub + return null; + } + + @Override + public String getAuthType() { + // Auto-generated method stub + return null; + } + + @Override + public Cookie[] getCookies() { + // Auto-generated method stub + return null; + } + + @Override + public long getDateHeader(String name) { + // Auto-generated method stub + return 0; + } + + @Override + public String getHeader(String name) { + return headers.get(name); + } + + @Override + public Enumeration getHeaders(String name) { + // Auto-generated method stub + return null; + } + + @Override + public Enumeration getHeaderNames() { + // Auto-generated method stub + return null; + } + + @Override + public int getIntHeader(String name) { + // Auto-generated method stub + return 0; + } + + @Override + public String getMethod() { + // Auto-generated method stub + return null; + } + + @Override + public String getPathInfo() { + // Auto-generated method stub + return null; + } + + @Override + public String getPathTranslated() { + // Auto-generated method stub + return null; + } + + @Override + public String getContextPath() { + // Auto-generated method stub + return null; + } + + @Override + public String getQueryString() { + // Auto-generated method stub + return null; + } + + @Override + public String getRemoteUser() { + // Auto-generated method stub + return null; + } + + @Override + public boolean isUserInRole(String role) { + // Auto-generated method stub + return false; + } + + @Override + public Principal getUserPrincipal() { + // Auto-generated method stub + return null; + } + + @Override + public String getRequestedSessionId() { + // Auto-generated method stub + return null; + } + + @Override + public String getRequestURI() { + // Auto-generated method stub + return null; + } + + @Override + public StringBuffer getRequestURL() { + // Auto-generated method stub + return null; + } + + @Override + public String getServletPath() { + // Auto-generated method stub + return null; + } + + @Override + public HttpSession getSession(boolean create) { + // Auto-generated method stub + return null; + } + + @Override + public HttpSession getSession() { + // Auto-generated method stub + return null; + } + + @Override + public String changeSessionId() { + // Auto-generated method stub + return null; + } + + @Override + public boolean isRequestedSessionIdValid() { + // Auto-generated method stub + return false; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + // Auto-generated method stub + return false; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + // Auto-generated method stub + return false; + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + // Auto-generated method stub + return false; + } + + @Override + public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { + // Auto-generated method stub + return false; + } + + @Override + public void login(String username, String password) throws ServletException { + // Auto-generated method stub + } + + @Override + public void logout() throws ServletException { + // Auto-generated method stub + } + + @Override + public Collection getParts() throws IOException, ServletException { + // Auto-generated method stub + return null; + } + + @Override + public Part getPart(String name) throws IOException, ServletException { + // Auto-generated method stub + return null; + } + + @Override + public T upgrade(Class handlerClass) throws IOException, ServletException { + // Auto-generated method stub + return null; + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/MockHttpServletResponse.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/MockHttpServletResponse.java new file mode 100644 index 00000000000..5494da825c9 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/MockHttpServletResponse.java @@ -0,0 +1,235 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +public class MockHttpServletResponse implements HttpServletResponse { + + Map headers = new HashMap<>(); + + public MockHttpServletResponse() { + } + + @Override + public String getCharacterEncoding() { + // Auto-generated method stub + return null; + } + + @Override + public String getContentType() { + // Auto-generated method stub + return null; + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + // Auto-generated method stub + return null; + } + + @Override + public PrintWriter getWriter() throws IOException { + // Auto-generated method stub + return null; + } + + @Override + public void setCharacterEncoding(String charset) { + // Auto-generated method stub + } + + @Override + public void setContentLength(int len) { + // Auto-generated method stub + } + + @Override + public void setContentLengthLong(long len) { + // Auto-generated method stub + } + + @Override + public void setContentType(String type) { + // Auto-generated method stub + } + + @Override + public void setBufferSize(int size) { + // Auto-generated method stub + } + + @Override + public int getBufferSize() { + // Auto-generated method stub + return 0; + } + + @Override + public void flushBuffer() throws IOException { + // Auto-generated method stub + } + + @Override + public void resetBuffer() { + // Auto-generated method stub + } + + @Override + public boolean isCommitted() { + // Auto-generated method stub + return false; + } + + @Override + public void reset() { + // Auto-generated method stub + } + + @Override + public void setLocale(Locale loc) { + // Auto-generated method stub + } + + @Override + public Locale getLocale() { + // Auto-generated method stub + return null; + } + + @Override + public void addCookie(Cookie cookie) { + // Auto-generated method stub + } + + @Override + public boolean containsHeader(String name) { + // Auto-generated method stub + return false; + } + + @Override + public String encodeURL(String url) { + // Auto-generated method stub + return null; + } + + @Override + public String encodeRedirectURL(String url) { + // Auto-generated method stub + return null; + } + + @Override + public String encodeUrl(String url) { + // Auto-generated method stub + return null; + } + + @Override + public String encodeRedirectUrl(String url) { + // Auto-generated method stub + return null; + } + + @Override + public void sendError(int sc, String msg) throws IOException { + // Auto-generated method stub + } + + @Override + public void sendError(int sc) throws IOException { + // Auto-generated method stub + } + + @Override + public void sendRedirect(String location) throws IOException { + // Auto-generated method stub + } + + @Override + public void setDateHeader(String name, long date) { + // Auto-generated method stub + } + + @Override + public void addDateHeader(String name, long date) { + // Auto-generated method stub + } + + @Override + public void setHeader(String name, String value) { + // Auto-generated method stub + } + + @Override + public void addHeader(String name, String value) { + headers.put(name, value); + } + + @Override + public void setIntHeader(String name, int value) { + // Auto-generated method stub + } + + @Override + public void addIntHeader(String name, int value) { + // Auto-generated method stub + } + + @Override + public void setStatus(int sc) { + // Auto-generated method stub + } + + @Override + public void setStatus(int sc, String sm) { + // Auto-generated method stub + } + + @Override + public int getStatus() { + // Auto-generated method stub + return 0; + } + + @Override + public String getHeader(String name) { + return headers.get(name); + } + + @Override + public Collection getHeaders(String name) { + // Auto-generated method stub + return null; + } + + @Override + public Collection getHeaderNames() { + // Auto-generated method stub + return null; + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/ResourceExceptionTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/ResourceExceptionTest.java new file mode 100644 index 00000000000..8891368510d --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/ResourceExceptionTest.java @@ -0,0 +1,72 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import static org.testng.Assert.*; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.zms.ResourceException; + + +public class ResourceExceptionTest { + + @Test + public void testCodeToString() { + + assertEquals("OK", ResourceException.codeToString(200)); + assertEquals("Created", ResourceException.codeToString(201)); + assertEquals("Accepted", ResourceException.codeToString(202)); + assertEquals("No Content", ResourceException.codeToString(204)); + assertEquals("Moved Permanently", ResourceException.codeToString(301)); + assertEquals("Found", ResourceException.codeToString(302)); + assertEquals("See Other", ResourceException.codeToString(303)); + assertEquals("Not Modified", ResourceException.codeToString(304)); + assertEquals("Temporary Redirect", ResourceException.codeToString(307)); + assertEquals("Bad Request", ResourceException.codeToString(400)); + assertEquals("Unauthorized", ResourceException.codeToString(401)); + assertEquals("Forbidden", ResourceException.codeToString(403)); + assertEquals("Not Found", ResourceException.codeToString(404)); + assertEquals("Conflict", ResourceException.codeToString(409)); + assertEquals("Gone", ResourceException.codeToString(410)); + assertEquals("Precondition Failed", ResourceException.codeToString(412)); + assertEquals("Unsupported Media Type", ResourceException.codeToString(415)); + assertEquals("Internal Server Error", ResourceException.codeToString(500)); + assertEquals("Not Implemented", ResourceException.codeToString(501)); + assertEquals("1001", ResourceException.codeToString(1001)); + } + + @Test + public void testCodeOnly() { + + ResourceException exc = new ResourceException(400); + assertEquals(exc.getData().toString(), "{code: 400, message: \"Bad Request\"}"); + } + + @Test + public void testGetData() { + + ResourceException exc = new ResourceException(400, "Invalid domain name"); + assertEquals(exc.getData(), "Invalid domain name"); + } + + @Test + public void testGetDataCast() { + + ResourceException exc = new ResourceException(400, new Integer(5000)); + assertEquals(exc.getData(Integer.class), new Integer(5000)); + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSDaemonTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSDaemonTest.java new file mode 100644 index 00000000000..0b63db949d1 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSDaemonTest.java @@ -0,0 +1,54 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import static org.testng.Assert.*; + +import org.apache.commons.daemon.DaemonContext; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +public class ZMSDaemonTest { + + @Test + public void testInit() throws Exception { + DaemonContext daemoncontext = Mockito.mock(DaemonContext.class); + ZMSDaemon daemon = new ZMSDaemon(); + daemon.init(daemoncontext); + } + + @Test + public void testStart() { + ZMSDaemon daemon = new ZMSDaemon(); + try { + daemon.start(); + } catch (Exception ex) { + assertTrue(true); + } + } + + @Test + public void testStop() throws Exception { + ZMSDaemon daemon = new ZMSDaemon(); + daemon.stop(); + } + + @Test + public void testDestroy() throws Exception { + ZMSDaemon daemon = new ZMSDaemon(); + daemon.destroy(); + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSImplTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSImplTest.java new file mode 100644 index 00000000000..aff66bbdc85 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSImplTest.java @@ -0,0 +1,14109 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.mockito.Mockito; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.*; + +import static org.testng.Assert.*; +import junit.framework.TestCase; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.WebApplicationException; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.PrincipalAuthority; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.impl.SimpleServiceIdentityProvider; +import com.yahoo.athenz.auth.token.PrincipalToken; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.metrics.MetricFactory; +import com.yahoo.athenz.common.server.log.AuditLogFactory; +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.common.utils.SignUtils; +import com.yahoo.athenz.provider.ProviderMockClient; +import com.yahoo.athenz.zms.ZMSImpl.AccessStatus; +import com.yahoo.athenz.zms.ZMSImpl.AthenzObject; +import com.yahoo.athenz.zms.store.AthenzDomain; +import com.yahoo.athenz.zms.store.ObjectStore; +import com.yahoo.athenz.zms.store.file.FileConnection; +import com.yahoo.athenz.zms.store.file.FileObjectStore; +import com.yahoo.athenz.zms.utils.ZMSUtils; +import com.yahoo.rdl.Schema; +import com.yahoo.rdl.Struct; + +public class ZMSImplTest extends TestCase { + + ZMSImpl zms = null; + String adminUser = null; + String pubKey = null; // assume default is K0 + String pubKeyK1 = null; + String pubKeyK2 = null; + String privKey = null; // assume default is K0 + String privKeyK1 = null; + String privKeyK2 = null; + String auditRef = "audittest"; + + // typically used when creating and deleting domains with all the tests + // + @Mock ZMSImpl.RsrcCtxWrapper mockDomRsrcCtx; + @Mock com.yahoo.athenz.common.server.rest.ResourceContext mockDomRestRsrcCtx; + Principal rsrcPrince = null; // used with the mockDomRestRsrcCtx + + @Mock ZMSImpl.RsrcCtxWrapper mockDomRsrcCtx2; + @Mock com.yahoo.athenz.common.server.rest.ResourceContext mockDomRestRsrcCtx2; + + private static final String MOCKCLIENTADDR = "10.11.12.13"; + @Mock HttpServletRequest mockServletRequest; + @Mock HttpServletResponse mockServletResponse; + + private static final String ZMS_DATA_STORE_PATH = "/tmp/zms_core_unit_tests/zms_root"; + + static final Struct TABLE_PROVIDER_ROLE_ACTIONS = new Struct() + .with("admin", "*").with("writer", "WRITE").with("reader", "READ"); + + static final Struct RESOURCE_PROVIDER_ROLE_ACTIONS = new Struct() + .with("writer", "WRITE").with("reader", "READ"); + + static final int BASE_PRODUCT_ID = 400000000; // these product ids will lie in 400 million range + static java.util.Random domainProductId = new java.security.SecureRandom(); + static synchronized int getRandomProductId() { + return BASE_PRODUCT_ID + domainProductId.nextInt(99999999); + } + + @BeforeClass(alwaysRun=true) + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Mockito.when(mockServletRequest.getRemoteAddr()).thenReturn(MOCKCLIENTADDR); + + System.setProperty(ZMSConsts.ZMS_PROP_METRIC_FACTORY_CLASS, ZMSConsts.ZMS_METRIC_FACTORY_CLASS); + System.setProperty(ZMSConsts.ZMS_PROP_STATS_ENABLED, "true"); + System.setProperty(ZMSConsts.ZMS_PROP_PROVIDER_ENDPOINTS, ".athenzcompany.com"); + + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_STORE_CLASS, "com.yahoo.athenz.zms.pkey.file.FilePrivateKeyStoreFactory"); + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY, "src/test/resources/zms_private.pem"); + System.setProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY, "src/test/resources/zms_public.pem"); + System.setProperty(ZMSConsts.ZMS_PROP_DOMAIN_ADMIN, "user.testadminuser"); + System.setProperty(ZMSConsts.ZMS_PROP_AUTHZ_SERVICE_FNAME, "src/test/resources/authorized_services.json"); + System.setProperty(ZMSConsts.ZMS_PROP_SOLUTION_TEMPLATE_FNAME, "src/test/resources/solution_templates.json"); + System.setProperty("logback.configurationFile", "src/test/resources/logback.xml"); + + setupServiceId(); + } + + com.yahoo.athenz.zms.ResourceContext createResourceContext(Principal prince) { + com.yahoo.athenz.common.server.rest.ResourceContext rsrcCtx = Mockito.mock(com.yahoo.athenz.common.server.rest.ResourceContext.class); + Mockito.when(rsrcCtx.principal()).thenReturn(prince); + Mockito.when(rsrcCtx.request()).thenReturn(mockServletRequest); + Mockito.when(rsrcCtx.response()).thenReturn(mockServletResponse); + + ZMSImpl.RsrcCtxWrapper rsrcCtxWrapper = Mockito.mock(ZMSImpl.RsrcCtxWrapper.class); + Mockito.when(rsrcCtxWrapper.context()).thenReturn(rsrcCtx); + Mockito.when(rsrcCtxWrapper.principal()).thenReturn(prince); + Mockito.when(rsrcCtxWrapper.request()).thenReturn(mockServletRequest); + Mockito.when(rsrcCtxWrapper.response()).thenReturn(mockServletResponse); + return rsrcCtxWrapper; + } + + com.yahoo.athenz.zms.ResourceContext createResourceContext(Principal principal, HttpServletRequest request) { + if (request == null) { + return createResourceContext(principal); + } + + com.yahoo.athenz.common.server.rest.ResourceContext rsrcCtx = Mockito.mock(com.yahoo.athenz.common.server.rest.ResourceContext.class); + Mockito.when(rsrcCtx.principal()).thenReturn(principal); + Mockito.when(rsrcCtx.request()).thenReturn(request); + Mockito.when(rsrcCtx.response()).thenReturn(mockServletResponse); + + ZMSImpl.RsrcCtxWrapper rsrcCtxWrapper = Mockito.mock(ZMSImpl.RsrcCtxWrapper.class); + Mockito.when(rsrcCtxWrapper.context()).thenReturn(rsrcCtx); + Mockito.when(rsrcCtxWrapper.request()).thenReturn(request); + Mockito.when(rsrcCtxWrapper.principal()).thenReturn(principal); + Mockito.when(rsrcCtxWrapper.response()).thenReturn(mockServletResponse); + return rsrcCtxWrapper; + } + + Object getWebAppExcEntity(javax.ws.rs.WebApplicationException wex) { + javax.ws.rs.core.Response resp = wex.getResponse(); + return resp.getEntity(); + } + + Object getWebAppExcMapValue(javax.ws.rs.WebApplicationException wex, String header) { + javax.ws.rs.core.MultivaluedMap mvmap = wex.getResponse().getMetadata(); + Object obj = mvmap.getFirst(header); + return obj; + } + + private Metric createMetric() { + MetricFactory metricFactory = null; + Metric metric = null; + try { + metricFactory = (MetricFactory) + Class.forName(System.getProperty(ZMSConsts.ZMS_PROP_METRIC_FACTORY_CLASS)).newInstance(); + metric = metricFactory.create(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException exc) { + System.out.println("Invalid MetricFactory class: " + ZMSConsts.ZMS_METRIC_FACTORY_CLASS + + " error: " + exc.getMessage()); + metric = new com.yahoo.athenz.common.metrics.impl.NoOpMetric(); + } + return metric; + } + + private ZMSImpl zmsInit() { + // we want to make sure we start we clean dir structure + FileConnection.deleteDirectory(new File(ZMS_DATA_STORE_PATH)); + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + String unsignedCreds = "v=U1;d=user;n=user1"; + rsrcPrince = SimplePrincipal.create("user", "user1", unsignedCreds + ";s=signature", + 0, principalAuthority); + ((SimplePrincipal) rsrcPrince).setUnsignedCreds(unsignedCreds); + + Mockito.when(mockDomRestRsrcCtx.request()).thenReturn(mockServletRequest); + Mockito.when(mockDomRestRsrcCtx.principal()).thenReturn(rsrcPrince); + Mockito.when(mockDomRsrcCtx.context()).thenReturn(mockDomRestRsrcCtx); + Mockito.when(mockDomRsrcCtx.request()).thenReturn(mockServletRequest); + Mockito.when(mockDomRsrcCtx.principal()).thenReturn(rsrcPrince); + + ObjectStore store = new FileObjectStore(new File(ZMS_DATA_STORE_PATH)); + + String pubKeyName = System.getProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY); + System.out.println("public key file=" + pubKeyName); + File pubKeyFile = new File(pubKeyName); + pubKey = Crypto.encodedFile(pubKeyFile); + + String privKeyName = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY); + System.out.println("private key file=" + privKeyName); + File privKeyFile = new File(privKeyName); + privKey = Crypto.encodedFile(privKeyFile); + PrivateKey privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + + String privKeyId = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_ID, "0"); + + adminUser = System.getProperty(ZMSConsts.ZMS_PROP_DOMAIN_ADMIN); + + Metric metric = createMetric(); + ZMSImpl zmsObj = new ZMSImpl("localhost", store, metric, privateKey, + privKeyId, pubKey, AuditLogFactory.getLogger(), null); + + ServiceIdentity service = createServiceObject("sys.auth", + "zms", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zmsObj.putServiceIdentity(mockDomRsrcCtx, "sys.auth", "zms", auditRef, service); + + return zmsObj; + } + + ZMSImpl getZmsImpl(String storeDir, AuditLogger alogger) { + + FileConnection.deleteDirectory(new File(storeDir)); + ObjectStore store = new FileObjectStore(new File(storeDir)); + String privKeyName = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY); + File privKeyFile = new File(privKeyName); + String privKey = Crypto.encodedFile(privKeyFile); + PrivateKey privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + + String privKeyId = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_ID, "0"); + ServiceIdentity service = createServiceObject("sys.auth", + "zms", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + Metric metric = createMetric(); + ZMSImpl zmsObj = new ZMSImpl("localhost", store, metric, privateKey, + privKeyId, pubKey, alogger, null); + zmsObj.putServiceIdentity(mockDomRsrcCtx, "sys.auth", "zms", auditRef, service); + zmsObj.setProviderClientClass(ProviderMockClient.class); + return zmsObj; + } + + public void setupServiceId() throws IOException { + + Path path = Paths.get("./src/test/resources/zms_public_k1.pem"); + pubKeyK1 = Crypto.ybase64((new String(Files.readAllBytes(path))).getBytes()); + + path = Paths.get("./src/test/resources/zms_public_k2.pem"); + pubKeyK2 = Crypto.ybase64(new String(Files.readAllBytes(path)).getBytes()); + + path = Paths.get("./src/test/resources/zms_private_k1.pem"); + privKeyK1 = Crypto.ybase64(new String(Files.readAllBytes(path)).getBytes()); + + path = Paths.get("./src/test/resources/zms_private_k2.pem"); + privKeyK2 = Crypto.ybase64(new String(Files.readAllBytes(path)).getBytes()); + + zms = zmsInit(); + zms.setProviderClientClass(ProviderMockClient.class); + } + + @AfterClass(alwaysRun=true) + public void shutdown() { + FileConnection.deleteDirectory(new File(ZMS_DATA_STORE_PATH)); + } + + private Membership generateMembership(String roleName, String memberName) { + Membership mbr = new Membership(); + mbr.setRoleName(roleName); + mbr.setMemberName(memberName); + mbr.setIsMember(true); + return mbr; + } + + private TopLevelDomain createTopLevelDomainObject(String name, + String description, String org, String admin) { + + TopLevelDomain dom = new TopLevelDomain(); + dom.setName(name); + dom.setDescription(description); + dom.setOrg(org); + dom.setYpmId(getRandomProductId()); + + List admins = new ArrayList(); + admins.add(admin); + dom.setAdminUsers(admins); + + return dom; + } + + private UserDomain createUserDomainObject(String name, String description, String org) { + + UserDomain dom = new UserDomain(); + dom.setName(name); + dom.setDescription(description); + dom.setOrg(org); + + return dom; + } + + private SubDomain createSubDomainObject(String name, String parent, + String description, String org, String admin) { + + SubDomain dom = new SubDomain(); + dom.setName(name); + dom.setDescription(description); + dom.setOrg(org); + dom.setParent(parent); + + List admins = new ArrayList(); + admins.add(admin); + dom.setAdminUsers(admins); + + return dom; + } + + private DomainMeta createDomainMetaObject(String description, String org, + Boolean futureEnabled, Boolean auditEnabled, String account, Integer productId) { + + DomainMeta meta = new DomainMeta(); + meta.setDescription(description); + meta.setOrg(org); + if (futureEnabled != null) { + meta.setEnabled(futureEnabled); + } + if (auditEnabled != null) { + meta.setAuditEnabled(auditEnabled); + } + if (account != null) { + meta.setAccount(account); + } + meta.setYpmId(productId); + + return meta; + } + + private Role createRoleObject(String domainName, String roleName, + String trust) { + Role role = new Role(); + role.setName(ZMSUtils.roleResourceName(domainName, roleName)); + role.setTrust(trust); + return role; + } + + private Role createRoleObject(String domainName, String roleName, + String trust, String member1, String member2) { + + List members = new ArrayList(); + if (member1 != null) { + members.add(member1); + } + if (member2 != null) { + members.add(member2); + } + return createRoleObject(domainName, roleName, trust, members); + } + + private Role createRoleObject(String domainName, String roleName, + String trust, List members) { + + Role role = new Role(); + role.setName(ZMSUtils.roleResourceName(domainName, roleName)); + role.setMembers(members); + if (trust != null) { + role.setTrust(trust); + } + + return role; + } + + private Policy createPolicyObject(String domainName, String policyName, + String roleName, String action, String resource, + AssertionEffect effect) { + return createPolicyObject(domainName, policyName, roleName, true, + action, resource, effect); + } + + private Policy createPolicyObject(String domainName, String policyName, + String roleName, boolean generateRoleName, String action, + String resource, AssertionEffect effect) { + + Policy policy = new Policy(); + policy.setName(ZMSUtils.policyResourceName(domainName, policyName)); + + Assertion assertion = new Assertion(); + assertion.setAction(action); + assertion.setEffect(effect); + assertion.setResource(resource); + if (generateRoleName) { + assertion.setRole(ZMSUtils.roleResourceName(domainName, roleName)); + } else { + assertion.setRole(roleName); + } + + List assertList = new ArrayList(); + assertList.add(assertion); + + policy.setAssertions(assertList); + return policy; + } + + private Policy createPolicyObject(String domainName, String policyName) { + return createPolicyObject(domainName, policyName, "Role1", "*", + domainName + ":*", AssertionEffect.ALLOW); + } + + private ServiceIdentity createServiceObject(String domainName, + String serviceName, String endPoint, String executable, + String user, String group, String host) { + + ServiceIdentity service = new ServiceIdentity(); + service.setExecutable(executable); + service.setName(ZMSUtils.serviceResourceName(domainName, serviceName)); + + List publicKeyList = new ArrayList(); + PublicKeyEntry publicKeyEntry1 = new PublicKeyEntry(); + publicKeyEntry1.setKey(pubKeyK1); + publicKeyEntry1.setId("1"); + publicKeyList.add(publicKeyEntry1); + PublicKeyEntry publicKeyEntry2 = new PublicKeyEntry(); + publicKeyEntry2.setKey(pubKeyK2); + publicKeyEntry2.setId("2"); + publicKeyList.add(publicKeyEntry2); + service.setPublicKeys(publicKeyList); + + service.setUser(user); + service.setGroup(group); + + if (endPoint != null) { + service.setProviderEndpoint(endPoint); + } + + List hosts = new ArrayList(); + hosts.add(host); + service.setHosts(hosts); + + return service; + } + + private Entity createEntityObject(String entityName) { + + Entity entity = new Entity(); + entity.setName(entityName); + + Struct value = new Struct(); + value.put("Key1", "Value1"); + entity.setValue(value); + + return entity; + } + + private void setupTenantDomainProviderService(ZMSImpl zms, String tenantDomain, String providerDomain, + String providerService, String providerEndpoint) { + + // create domain for tenant + // + TopLevelDomain dom1 = createTopLevelDomainObject(tenantDomain, + "Test Tenant Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // create domain for provider + // + TopLevelDomain domProv = createTopLevelDomainObject(providerDomain, + "Test Provider Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, domProv); + + // create service identity for providerDomain.providerService + // + ServiceIdentity service = createServiceObject( + providerDomain, providerService, providerEndpoint, + "/usr/bin/java", "root", "users", "localhost"); + + zms.putServiceIdentity(mockDomRsrcCtx, providerDomain, providerService, auditRef, service); + } + + private void setupTenantDomainProviderService(String tenantDomain, String providerDomain, + String providerService, String providerEndpoint) { + setupTenantDomainProviderService(zms, tenantDomain, providerDomain, providerService, providerEndpoint); + } + + private Tenancy createTenantObject(String domain, String service) { + + Tenancy tenant = new Tenancy(); + tenant.setDomain(domain); + tenant.setService(service); + + return tenant; + } + + @Test + public void testSchema() { + Schema schema = zms.schema(); + assertNotNull(schema); + } + + @Test + public void testGetAuditLogMsgBuilder() { + AuditLogMsgBuilder msgBldr = ZMSImpl.getAuditLogMsgBuilder(mockDomRsrcCtx, "mydomain", auditRef, "myapi", "PUT"); + assertNotNull(msgBldr); + } + + @Test + public void testGetAuditLogMsgBuilderNullCtx() { + AuditLogMsgBuilder msgBldr = ZMSImpl.getAuditLogMsgBuilder(null, "mydomain", auditRef, "myapi", "PUT"); + assertNotNull(msgBldr); + } + + @Test + public void testGetAuditLogMsgBuilderNullPrincipal() { + ResourceContext ctx = createResourceContext(null); + AuditLogMsgBuilder msgBldr = ZMSImpl.getAuditLogMsgBuilder(ctx, "mydomain", auditRef, "myapi", "PUT"); + assertNotNull(msgBldr); + } + + @Test + public void testGetAuditLogMsgBuilderTokenWithSig() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + String userId = "user1"; + String signature = "ABRACADABRA"; + String unsignedCreds = "v=U1;d=user;n=user1"; + Principal principal = SimplePrincipal.create("user", userId, unsignedCreds + ";s=" + signature, + 0, principalAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(unsignedCreds); // set unsigned creds + ResourceContext ctx = createResourceContext(principal); + AuditLogMsgBuilder msgBldr = ZMSImpl.getAuditLogMsgBuilder(ctx, "mydomain", auditRef, "myapi", "PUT"); + assertNotNull(msgBldr); + String who = msgBldr.who(); + assertNotNull(who); + assertTrue(who.contains(userId)); + assertTrue("Should not contain the signature: " + who, !who.contains(signature)); + } + + @Test + public void testGetAuditLogMsgBuilderTokenSigMissing() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + String userId = "user1"; + String unsignedCreds = "v=U1;d=user;n=user1"; + Principal principal = SimplePrincipal.create("user", userId, unsignedCreds, + 0, principalAuthority); + ResourceContext ctx = createResourceContext(principal); + AuditLogMsgBuilder msgBldr = ZMSImpl.getAuditLogMsgBuilder(ctx, "mydomain", auditRef, "myapi", "PUT"); + assertNotNull(msgBldr); + String who = msgBldr.who(); + assertNotNull(who); + assertTrue(who.contains(userId)); + } + + @Test + public void testGetAuditLogMsgBuilderNullParams() { + AuditLogMsgBuilder msgBldr = ZMSImpl.getAuditLogMsgBuilder(mockDomRsrcCtx, null, null, null, null); + assertNotNull(msgBldr); + } + + @Test + public void testGetDomain() { + Domain domain = zms.getDomain(mockDomRsrcCtx, "sys.auth"); + assertNotNull(domain); + } + + @Test + public void testGetDomainThrowException() { + try { + zms.getDomain(mockDomRsrcCtx, "wrongDomain"); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + } + + @Test(groups="post-domain-tests") + public void testPostDomain() { + String domName = "olddominion"; + try { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domName, auditRef); + } catch (ResourceException re) { + assertEquals(re.getCode(), 404); + } + + TopLevelDomain dom = new TopLevelDomain(); + dom.setName(domName); + dom.setDescription("old virginny"); + dom.setOrg("universities"); + dom.setYpmId(1930); + + List admins = new ArrayList(); + admins.add(adminUser); + dom.setAdminUsers(admins); + + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + // post subdomain + String subDomName = "extension"; + SubDomain subDom = createSubDomainObject(subDomName, domName, + "old dominion extension", "education", adminUser); + zms.postSubDomain(mockDomRsrcCtx, domName, auditRef, subDom); + + // post sub domain that is too big - default length is 128 + String subDomNameBad = "extension0extension0extension0extension0"; + subDomNameBad = subDomNameBad.concat("extension0extension0extension0extension0"); + subDomNameBad = subDomNameBad.concat("extension0extension0extension0extension0"); + + subDom = createSubDomainObject(subDomNameBad, domName, + "old dominion extension+++", "education", adminUser); + try { + zms.postSubDomain(mockDomRsrcCtx, domName, auditRef, subDom); + fail("requesterror not thrown."); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains("Invalid SubDomain name")); + assertTrue(ex.getMessage().contains("name length cannot exceed")); + } + + zms.deleteSubDomain(mockDomRsrcCtx, domName, subDomName, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, domName, auditRef); + } + + @Test(groups="post-domain-tests") + public void testPostDomainNullObject() { + try { + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, null); + fail("requesterror not thrown."); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test(groups="post-domain-tests") + public void testPostTopLevelDomainNoProductId() { + + // enable product id support + + System.setProperty(ZMSConsts.ZMS_PROP_PRODUCT_ID_SUPPORT, "true"); + ZMSImpl zmsImpl = zmsInit(); + + String domName = "jabberwocky"; + try { + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domName, auditRef); + } catch (ResourceException re) { + assertEquals(re.getCode(), 404); + } + + TopLevelDomain dom = new TopLevelDomain(); + dom.setName(domName); + dom.setDescription("mythic animal"); + dom.setOrg("animals"); + + List admins = new ArrayList(); + admins.add(adminUser); + dom.setAdminUsers(admins); + + try { + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + fail("requesterror not thrown."); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains("Product Id is required when creating top level domain")); + } + + System.clearProperty(ZMSConsts.ZMS_PROP_PRODUCT_ID_SUPPORT); + } + + @Test(groups="post-domain-tests") + public void testPostTopLevelDomainNameTooLong() { + String domName = "a234567890"; + StringBuilder dname = new StringBuilder(); + dname.append(domName).append(domName).append(domName).append(domName); + dname.append(domName).append(domName).append(domName).append(domName); + dname.append(domName).append(domName).append(domName).append(domName); + dname.append("a23456789"); // have 129 chars - default is 128 + domName = dname.toString(); + try { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domName, auditRef); + } catch (ResourceException re) { + assertEquals(re.getCode(), 404); + } + + TopLevelDomain dom = new TopLevelDomain(); + dom.setName(domName); + dom.setDescription("bigun"); + dom.setOrg("bigdog"); + + List admins = new ArrayList(); + admins.add(adminUser); + dom.setAdminUsers(admins); + + try { + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + fail("requesterror not thrown."); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains("name length cannot exceed")); + } + } + + @Test(groups="post-domain-tests") + public void testPostDomainNameOnSizeLimit() { + + // set the domain size limit to 45 + System.setProperty(ZMSConsts.ZMS_PROP_DOMAIN_NAME_MAX_SIZE, "45"); + + ZMSImpl zmsImpl = zmsInit(); + + String domName = "a234567890"; + StringBuilder dname = new StringBuilder(); + dname.append(domName).append(domName).append(domName).append(domName); + dname.append("a2345"); // have 45 chars + domName = dname.toString(); + try { + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domName, auditRef); + } catch (ResourceException re) { + assertEquals(re.getCode(), 404); + } + + TopLevelDomain dom = new TopLevelDomain(); + dom.setName(domName); + dom.setDescription("bigun"); + dom.setOrg("bigdog"); + dom.setYpmId(999999); + + List admins = new ArrayList(); + admins.add(adminUser); + dom.setAdminUsers(admins); + + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + // post sub domain which will be too big by 1 char + String subDomNameBad = "B"; + SubDomain subDom = createSubDomainObject(subDomNameBad, domName, + "1 char too many", "dogs", adminUser); + try { + zmsImpl.postSubDomain(mockDomRsrcCtx, domName, auditRef, subDom); + fail("requesterror not thrown."); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains("Invalid SubDomain name")); + assertTrue(ex.getMessage().contains("name length cannot exceed")); + } + + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domName, auditRef); + System.clearProperty(ZMSConsts.ZMS_PROP_DOMAIN_NAME_MAX_SIZE); + } + + @Test + public void testPostTopLevelDomainNameReduceSizeLimitTooSmall() { + + // lower name length to 5 which should be reset internally to the default + System.setProperty(ZMSConsts.ZMS_PROP_DOMAIN_NAME_MAX_SIZE, "5"); + ZMSImpl zmsImpl = zmsInit(); + + String domName = "abcdef7890"; + try { + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domName, auditRef); + } catch (ResourceException re) { + assertEquals(re.getCode(), 404); + } + + TopLevelDomain dom = new TopLevelDomain(); + dom.setName(domName); + dom.setDescription("bigun"); + dom.setOrg("bigdog"); + dom.setYpmId(77777); + + List admins = new ArrayList(); + admins.add(adminUser); + dom.setAdminUsers(admins); + + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domName, auditRef); + System.clearProperty(ZMSConsts.ZMS_PROP_DOMAIN_NAME_MAX_SIZE); + } + + @Test + public void testGetDomainList() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ListDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("ListDom2", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + DomainList domList = zms.getDomainList(mockDomRsrcCtx, null, null, null, null, + null, null, null, null, null); + assertNotNull(domList); + + assertTrue(domList.getNames().contains("ListDom1".toLowerCase())); + assertTrue(domList.getNames().contains("ListDom2".toLowerCase())); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ListDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ListDom2", auditRef); + } + + @Test + public void testGetDomainListByAccount() { + + String domainName = "lookupdomainaccount"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + dom1.setAccount("1234"); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + DomainList domList = zms.getDomainList(mockDomRsrcCtx, null, null, null, null, + "1234", null, null, null, null); + assertNotNull(domList.getNames()); + assertEquals(domList.getNames().size(), 1); + assertEquals(domList.getNames().get(0), domainName); + + domList = zms.getDomainList(mockDomRsrcCtx, null, null, null, null, + "1235", null, null, null, null); + assertNull(domList.getNames()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetDomainListByProductId() { + + String domainName = "lookupdomainbyproductid"; + + // enable product id support + + System.setProperty(ZMSConsts.ZMS_PROP_PRODUCT_ID_SUPPORT, "true"); + ZMSImpl zmsImpl = zmsInit(); + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + dom1.setYpmId(Integer.valueOf(101)); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + DomainList domList = zmsImpl.getDomainList(mockDomRsrcCtx, null, null, null, + null, null, Integer.valueOf(101), null, null, null); + assertNotNull(domList.getNames()); + assertEquals(domList.getNames().size(), 1); + assertEquals(domList.getNames().get(0), domainName); + + domList = zmsImpl.getDomainList(mockDomRsrcCtx, null, null, null, null, null, + Integer.valueOf(102), null, null, null); + assertNull(domList.getNames()); + + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + System.clearProperty(ZMSConsts.ZMS_PROP_PRODUCT_ID_SUPPORT); + } + + @Test + public void testGetDomainListIfModifiedSince() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ListDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // let's get the current time + + Date now = new Date(); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + + TopLevelDomain dom2 = createTopLevelDomainObject("ListDom2", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + DateFormat df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz"); + String modifiedSince = df.format(now); + + // this is only a partial list since our file struct store + // which the unit tests use does not support last modified + // option so this will be tested in zms_system_test package + + DomainList domList = zms.getDomainList(mockDomRsrcCtx, null, null, null, + null, null, null, null, null, modifiedSince); + assertNotNull(domList); + + assertTrue(domList.getNames().contains("ListDom2".toLowerCase())); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ListDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ListDom2", auditRef); + } + + @Test + public void testGetDomainListInvalidIfModifiedSince() { + + try { + zms.getDomainList(mockDomRsrcCtx, null, null, null, null, null, + null, null, null, "abc"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + try { + zms.getDomainList(mockDomRsrcCtx, null, null, null, null, null, + null, null, null, "May 20, 1099"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + try { + zms.getDomainList(mockDomRsrcCtx, null, null, null, null, null, + null, null, null, "03:03:20 PM"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testGetDomainListParamsLimit() { + + TopLevelDomain dom1 = createTopLevelDomainObject("LimitDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("LimitDom2", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + DomainList domList = zms.getDomainList(mockDomRsrcCtx, 1, null, null, + null, null, null, null, null, null); + assertTrue(domList.getNames().size() == 1); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "LimitDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "LimitDom2", auditRef); + } + + @Test + public void testGetDomainListParamsSkip() { + + TopLevelDomain dom1 = createTopLevelDomainObject("SkipDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("SkipDom2", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + TopLevelDomain dom3 = createTopLevelDomainObject("SkipDom3", + "Test Domain3", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom3); + + DomainList domList = zms.getDomainList(mockDomRsrcCtx, null, null, null, + null, null, null, null, null, null); + int size = domList.getNames().size(); + assertTrue(size > 3); + + // ask for only for 2 domains back + domList = zms.getDomainList(mockDomRsrcCtx, 2, null, null, null, null, + null, null, null, null); + assertEquals(domList.getNames().size(), 2); + + // ask for the remaining domains + DomainList remList = zms.getDomainList(mockDomRsrcCtx, null, domList.getNext(), + null, null, null, null, null, null, null); + assertEquals(remList.getNames().size(), size - 2); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "SkipDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "SkipDom2", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "SkipDom3", auditRef); + } + + @Test + public void testGetDomainListParamsPrefix() { + + TopLevelDomain dom1 = createTopLevelDomainObject("NoPrefixDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("PrefixDom2", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + DomainList domList = zms.getDomainList(mockDomRsrcCtx, null, null, + "Prefix", null, null, null, null, null, null); + + assertFalse(domList.getNames().contains("NoPrefixDom1".toLowerCase())); + assertTrue(domList.getNames().contains("PrefixDom2".toLowerCase())); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "NoPrefixDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "PrefixDom2", auditRef); + } + + @Test + public void testGetDomainListParamsDepth() { + + TopLevelDomain dom1 = createTopLevelDomainObject("DepthDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + SubDomain dom2 = createSubDomainObject("DepthDom2", "DepthDom1", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "DepthDom1", auditRef, dom2); + + SubDomain dom3 = createSubDomainObject("DepthDom3", + "DepthDom1.DepthDom2", "Test Domain3", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "DepthDom1.DepthDom2", auditRef, dom3); + + DomainList domList = zms.getDomainList(mockDomRsrcCtx, null, null, null, + 1, null, null, null, null, null); + + assertTrue(domList.getNames().contains("DepthDom1".toLowerCase())); + assertTrue(domList.getNames().contains("DepthDom1.DepthDom2".toLowerCase())); + assertFalse(domList.getNames().contains("DepthDom1.DepthDom2.DepthDom3".toLowerCase())); + + zms.deleteSubDomain(mockDomRsrcCtx, "DepthDom1.DepthDom2", "DepthDom3", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "DepthDom1", "DepthDom2", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "DepthDom1", auditRef); + } + + @Test + public void testGetDomainListThrowException() { + try { + zms.getDomainList(mockDomRsrcCtx, -1, null, null, null, null, null, null, null, null); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } + } + + @Test + public void testCreateTopLevelDomain() { + + TopLevelDomain dom1 = createTopLevelDomainObject("AddTopDom1", + "Test Domain1", "testOrg", adminUser); + Domain resDom1 = zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + assertNotNull(resDom1); + + Domain resDom2 = zms.getDomain(mockDomRsrcCtx, "AddTopDom1"); + assertNotNull(resDom2); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "AddTopDom1", auditRef); + } + + @Test + public void testCreateTopLevelDomainOnceOnly() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_posttopdomonceonly"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + TopLevelDomain dom1 = createTopLevelDomainObject("AddOnceTopDom1", + "Test Domain1", "testOrg", adminUser); + Domain resDom1 = zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + assertNotNull(resDom1); + + // we should get an exception for the second call + + try { + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + String caller = "posttopleveldomain"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + if (msg.indexOf("ERROR=(") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(makeDomain: Cannot create domain: addoncetopdom1 - already exists)") != -1); + foundError = true; + break; + } + assertTrue(foundError); + + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, "AddOnceTopDom1", auditRef); + } + + @Test + public void testCreateSubDomain() { + + TopLevelDomain dom1 = createTopLevelDomainObject("AddSubDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + SubDomain dom2 = createSubDomainObject("AddSubDom2", "AddSubDom1", + "Test Domain2", "testOrg", adminUser); + Domain resDom1 = zms.postSubDomain(mockDomRsrcCtx, "AddSubDom1", auditRef, dom2); + assertNotNull(resDom1); + + Domain resDom2 = zms.getDomain(mockDomRsrcCtx, "AddSubDom1.AddSubDom2"); + assertNotNull(resDom2); + + zms.deleteSubDomain(mockDomRsrcCtx, "AddSubDom1", "AddSubDom2", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "AddSubDom1", auditRef); + } + + @Test + public void testCreateUserDomain() { + + UserDomain dom1 = createUserDomainObject("hga", "Test Domain1", "testOrg"); + zms.postUserDomain(mockDomRsrcCtx, "hga", auditRef, dom1); + + Domain resDom2 = zms.getDomain(mockDomRsrcCtx, "user.hga"); + assertNotNull(resDom2); + + zms.deleteUserDomain(mockDomRsrcCtx, "hga", auditRef); + } + + + @Test + public void testCreateUserDomainMismatch() { + + UserDomain dom1 = createUserDomainObject("hga", "Test Domain1", "testOrg"); + try { + zms.postUserDomain(mockDomRsrcCtx, "hga2", auditRef, dom1); + } catch (ResourceException ex) { + assertEquals(403, ex.getCode()); + } + } + + @Test + public void testDeleteUserDomain() { + + UserDomain dom1 = createUserDomainObject("hga", "Test Domain1", "testOrg"); + zms.postUserDomain(mockDomRsrcCtx, "hga", auditRef, dom1); + + zms.deleteUserDomain(mockDomRsrcCtx, "hga", auditRef); + + try { + zms.getDomain(mockDomRsrcCtx, "hga"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + } + + @Test + public void testCreateSubDomainWithVirtualLimit() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT, "2"); + ZMSImpl zmsTest = zmsInit(); + + TopLevelDomain dom1 = createTopLevelDomainObject("SubDomNoVirtual", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + SubDomain dom = createSubDomainObject("sub1", "SubDomNoVirtual", + "Test Domain2", "testOrg", adminUser); + Domain resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "SubDomNoVirtual", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub2", "SubDomNoVirtual", + "Test Domain2", "testOrg", adminUser); + resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "SubDomNoVirtual", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub3", "SubDomNoVirtual", + "Test Domain2", "testOrg", adminUser); + resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "SubDomNoVirtual", auditRef, dom); + assertNotNull(resDom); + + zms.deleteSubDomain(mockDomRsrcCtx, "SubDomNoVirtual", "sub3", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "SubDomNoVirtual", "sub2", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "SubDomNoVirtual", "sub1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "SubDomNoVirtual", auditRef); + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT); + } + + @Test + public void testCreateSubDomainVirtual() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT, "5"); + ZMSImpl zmsTest = zmsInit(); + + SubDomain dom = createSubDomainObject("sub1", "user.user1", + "Test Domain2", "testOrg", adminUser); + Domain resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub2", "user.user1", + "Test Domain2", "testOrg", adminUser); + resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub3", "user.user1", + "Test Domain2", "testOrg", adminUser); + resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub1a", "user.user1.sub1", + "Test Domain2", "testOrg", adminUser); + resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1.sub1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub1aa", "user.user1.sub1.sub1a", + "Test Domain2", "testOrg", adminUser); + resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1.sub1.sub1a", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub1ab", "user.user1.sub1.sub1a", + "Test Domain2", "testOrg", adminUser); + try { + zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1.sub1.sub1a", auditRef, dom); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + } + + dom = createSubDomainObject("sub4", "user.user1", + "Test Domain2", "testOrg", adminUser); + try { + zms.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + } + + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1.sub1.sub1a", "sub1aa", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1.sub1", "sub1a", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub3", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub2", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub1", auditRef); + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT); + } + + @Test + public void testCreateSubDomainVirtualNoLimit() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT, "0"); + ZMSImpl zmsTest = zmsInit(); + + SubDomain dom = createSubDomainObject("sub1", "user.user1", + "Test Domain2", "testOrg", adminUser); + Domain resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub2", "user.user1", + "Test Domain2", "testOrg", adminUser); + resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub3", "user.user1", + "Test Domain2", "testOrg", adminUser); + resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub4", "user.user1", + "Test Domain2", "testOrg", adminUser); + resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub5", "user.user1", + "Test Domain2", "testOrg", adminUser); + resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub6", "user.user1", + "Test Domain2", "testOrg", adminUser); + resDom = zmsTest.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub6", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub5", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub4", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub3", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub2", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub1", auditRef); + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT); + } + + @Test + public void testCreateSubDomainMismatchParent() { + + TopLevelDomain dom1 = createTopLevelDomainObject("AddSubMismatchParentDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + SubDomain dom2 = createSubDomainObject("AddSubDom2", "AddSubMismatchParentDom1", + "Test Domain2", "testOrg", adminUser); + + try { + zms.postSubDomain(mockDomRsrcCtx, "AddSubMismatchParentDom2", auditRef, dom2); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 403); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "AddSubMismatchParentDom1", auditRef); + } + + @Test + public void testCreateSubdomainOnceOnly() { + + TopLevelDomain dom1 = createTopLevelDomainObject("AddOnceSubDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + SubDomain dom2 = createSubDomainObject("AddOnceSubDom2", + "AddOnceSubDom1", "Test Domain2", "testOrg", adminUser); + Domain resDom1 = zms.postSubDomain(mockDomRsrcCtx, "AddOnceSubDom1", auditRef, dom2); + assertNotNull(resDom1); + + // we should get an exception for the second call + + try { + zms.postSubDomain(mockDomRsrcCtx, "AddOnceSubDom1", auditRef, dom2); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + zms.deleteSubDomain(mockDomRsrcCtx, "AddOnceSubDom1", "AddOnceSubDom2", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "AddOnceSubDom1", auditRef); + } + + @Test + public void testDeleteDomain() { + TopLevelDomain dom = createTopLevelDomainObject( + "TestDeleteDomain", null, null, adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + zms.deleteDomain(mockDomRsrcCtx, auditRef, "testdeletedomain", "testDeleteDomain"); + + try { + zms.getDomain(mockDomRsrcCtx, "TestDeleteDomain"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testDeleteDomainNonExistant() { + try { + zms.deleteDomain(mockDomRsrcCtx, auditRef, "TestDeleteDomainNonExist", "testDeleteDomainNonExistant"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testDeleteDomainMissingAuditRef() { + // create domain and require auditing + String domain = "testdeletedomainmissingauditref"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, null, null, adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + // delete it without an auditRef and catch exception + try { + zms.deleteDomain(mockDomRsrcCtx, null, domain, "testDeleteDomainMissingAuditRef"); + fail("requesterror not thrown by testDeleteDomainMissingAuditRef."); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testDeleteTopLevelDomain() { + + TopLevelDomain dom1 = createTopLevelDomainObject("DelTopDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Domain resDom1 = zms.getDomain(mockDomRsrcCtx, "DelTopDom1"); + assertNotNull(resDom1); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "DelTopDom1", auditRef); + + // we should get a forbidden exception since the domain + // no longer exists + + try { + zms.getDomain(mockDomRsrcCtx, "DelTopDom1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteTopLevelDomainChildExist() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_deltopdomhrowexc"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String caller = "deletetopleveldomain"; + + TopLevelDomain dom1 = createTopLevelDomainObject("DelTopChildDom1", + "Test Domain1", "testOrg", adminUser); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + SubDomain dom2 = createSubDomainObject("DelSubDom2", "DelTopChildDom1", + "Test Domain2", "testOrg", adminUser); + zmsImpl.postSubDomain(mockDomRsrcCtx, "DelTopChildDom1", auditRef, dom2); + + // we can't delete Dom1 since Dom2 still exists + + try { + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, "DelTopChildDom1", auditRef); + fail("requesterror not thrown."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(deletetopleveldomain: Cannot delete domain deltopchilddom1: 1 subdomains of it exist))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + + zmsImpl.deleteSubDomain(mockDomRsrcCtx, "DelTopChildDom1", "DelSubDom2", auditRef); + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, "DelTopChildDom1", auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + + @Test + public void testDeleteTopLevelDomainNonExistant() { + try { + zms.deleteTopLevelDomain(mockDomRsrcCtx, "NonExistantDomain", auditRef); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testDeleteTopLevelDomainNonExistantNoAuditRef() { + try { + zms.deleteTopLevelDomain(mockDomRsrcCtx, "NonExistantDomain", null); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testDeleteTopLevelDomainMissingAuditRef() { + // create domain and require auditing + TopLevelDomain dom = createTopLevelDomainObject( + "TopDomainAuditRequired", null, null, adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + // delete it without an auditRef and catch exception + try { + zms.deleteTopLevelDomain(mockDomRsrcCtx, "TopDomainAuditRequired", null); + fail("requesterror not thrown by deleteTopLevelDomain."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, "TopDomainAuditRequired", auditRef); + } + } + + @Test + public void testDeleteSubDomain() { + + TopLevelDomain dom1 = createTopLevelDomainObject("DelSubDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + SubDomain dom2 = createSubDomainObject("DelSubDom2", "DelSubDom1", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "DelSubDom1", auditRef, dom2); + + Domain resDom1 = zms.getDomain(mockDomRsrcCtx, "DelSubDom1.DelSubDom2"); + assertNotNull(resDom1); + + zms.deleteSubDomain(mockDomRsrcCtx, "DelSubDom1", "DelSubDom2", auditRef); + + // we should get a forbidden exception since the domain + // no longer exists + + try { + zms.getDomain(mockDomRsrcCtx, "DelSubDom1.DelSubDom2"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "DelSubDom1", auditRef); + } + + @Test + public void testDeleteSubDomainChildExist() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_delsubdomchildexist"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String caller = "deletesubdomain"; + + TopLevelDomain dom1 = createTopLevelDomainObject("DelSubChildDom1", + "Test Domain1", "testOrg", adminUser); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + SubDomain dom2 = createSubDomainObject("DelSubDom2", "DelSubChildDom1", + "Test Domain2", "testOrg", adminUser); + zmsImpl.postSubDomain(mockDomRsrcCtx, "DelSubChildDom1", auditRef, dom2); + + SubDomain dom3 = createSubDomainObject("DelSubDom3", "DelSubChildDom1.DelSubDom2", + "Test Domain3", "testOrg", adminUser); + zmsImpl.postSubDomain(mockDomRsrcCtx, "DelSubChildDom1.DelSubDom2", auditRef, dom3); + + // we can't delete Dom2 since Dom3 still exists + + try { + zmsImpl.deleteSubDomain(mockDomRsrcCtx, "DelSubChildDom1", "DelSubDom2", auditRef); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(deletesubdomain: Cannot delete domain delsubchilddom1.delsubdom2: 1 subdomains of it exist))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + + zmsImpl.deleteSubDomain(mockDomRsrcCtx, "DelSubChildDom1.DelSubDom2", "DelSubDom3", auditRef); + zmsImpl.deleteSubDomain(mockDomRsrcCtx, "DelSubChildDom1", "DelSubDom2", auditRef); + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, "DelSubChildDom1", auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + + @Test + public void testDeleteSubDomainNonExistant() { + TopLevelDomain dom = createTopLevelDomainObject( + "ExistantTopDomain", null, null, adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + try { + zms.deleteSubDomain(mockDomRsrcCtx, "ExistantTopDomain", "NonExistantSubDomain", auditRef); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ExistantTopDomain", auditRef); + } + + @Test + public void testDeleteSubDomainSubAndTopNonExistant() { + try { + zms.deleteSubDomain(mockDomRsrcCtx, "NonExistantTopDomain", "NonExistantSubDomain", auditRef); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testDeleteSubDomainMissingAuditRef() { + TopLevelDomain dom = createTopLevelDomainObject( + "ExistantTopDomain2", null, null, adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + SubDomain subDom = createSubDomainObject( + "ExistantSubDom2", "ExistantTopDomain2", + null, null, adminUser); + subDom.setAuditEnabled(true); + zms.postSubDomain(mockDomRsrcCtx, "ExistantTopDomain2", auditRef, subDom); + + try { + zms.deleteSubDomain(mockDomRsrcCtx, "ExistantTopDomain2", "ExistantSubDom2", null); + fail("requesterror not thrown by deleteSubDomain."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteSubDomain(mockDomRsrcCtx, "ExistantTopDomain2", "ExistantSubDom2", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ExistantTopDomain2", auditRef); + } + } + + @Test + public void testPutDomainMetaThrowException() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_putdommetathrowexc"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String caller = "putdomainmeta"; + String domName = "wrongDomainName"; + DomainMeta meta = new DomainMeta(); + meta.setYpmId(getRandomProductId()); + try { + zmsImpl.putDomainMeta(mockDomRsrcCtx, domName, auditRef, meta); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(404, e.getCode()); + } + + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(putdomainmeta: Unknown domain: wrongdomainname))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + } + + @Test(groups="post-domain-tests") + public void testPutDomainMeta() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MetaDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Domain resDom1 = zms.getDomain(mockDomRsrcCtx, "MetaDom1"); + assertNotNull(resDom1); + assertEquals(resDom1.getDescription(), "Test Domain1"); + assertEquals(resDom1.getOrg(), "testOrg"); + assertTrue(resDom1.getEnabled()); + assertFalse(resDom1.getAuditEnabled()); + + DomainMeta meta = createDomainMetaObject("Test2 Domain", "NewOrg", + false, true, "12345", 1001); + zms.putDomainMeta(mockDomRsrcCtx, "MetaDom1", auditRef, meta); + + Domain resDom3 = zms.getDomain(mockDomRsrcCtx, "MetaDom1"); + assertNotNull(resDom3); + assertEquals(resDom3.getDescription(), "Test2 Domain"); + assertEquals(resDom3.getOrg(), "NewOrg"); + assertTrue(resDom3.getEnabled()); + assertTrue(resDom3.getAuditEnabled()); + assertEquals("12345", resDom3.getAccount()); + assertEquals(Integer.valueOf(1001), resDom3.getYpmId()); + + // put the meta data using same product id + meta.setDescription("just a new desc"); + meta.setOrg("organs"); + zms.putDomainMeta(mockDomRsrcCtx, "MetaDom1", auditRef, meta); + + resDom3 = zms.getDomain(mockDomRsrcCtx, "MetaDom1"); + assertNotNull(resDom3); + assertEquals(resDom3.getDescription(), "just a new desc"); + assertEquals(resDom3.getOrg(), "organs"); + assertTrue(resDom3.getEnabled()); + assertTrue(resDom3.getAuditEnabled()); + assertEquals("12345", resDom3.getAccount()); + assertEquals(Integer.valueOf(1001), resDom3.getYpmId()); + + // put the meta data using new product + Integer newProductId = getRandomProductId(); + meta.setYpmId(newProductId); + zms.putDomainMeta(mockDomRsrcCtx, "MetaDom1", auditRef, meta); + + resDom3 = zms.getDomain(mockDomRsrcCtx, "MetaDom1"); + assertNotNull(resDom3); + assertEquals(resDom3.getDescription(), "just a new desc"); + assertEquals(resDom3.getOrg(), "organs"); + assertTrue(resDom3.getEnabled()); + assertTrue(resDom3.getAuditEnabled()); + assertEquals("12345", resDom3.getAccount()); + assertEquals(newProductId, resDom3.getYpmId()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MetaDom1", auditRef); + } + + @Test(groups="post-domain-tests") + public void testPutDomainMetaInvalid() { + + // enable product id support + + System.setProperty(ZMSConsts.ZMS_PROP_PRODUCT_ID_SUPPORT, "true"); + ZMSImpl zmsImpl = zmsInit(); + + TopLevelDomain dom = createTopLevelDomainObject("MetaDomProductid", + "Test Domain", "testOrg", adminUser); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Domain resDom = zmsImpl.getDomain(mockDomRsrcCtx, "MetaDomProductid"); + assertNotNull(resDom); + assertEquals(resDom.getDescription(), "Test Domain"); + assertEquals(resDom.getOrg(), "testOrg"); + assertTrue(resDom.getEnabled()); + assertFalse(resDom.getAuditEnabled()); + Integer productId = resDom.getYpmId(); + + DomainMeta meta = createDomainMetaObject("Test2 Domain", "NewOrg", + false, true, "12345", null); + try { + zmsImpl.putDomainMeta(mockDomRsrcCtx, "MetaDomProductid", auditRef, meta); + fail("bad request exc not thrown"); + } catch (ResourceException exc) { + assertEquals(400, exc.getCode()); + assertTrue(exc.getMessage().contains("Unique Product Id must be specified for top level domain")); + } + + // put meta data using another domains productId + dom = createTopLevelDomainObject("MetaDomProductid2", + "Test Domain", "testOrg", adminUser); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + resDom = zmsImpl.getDomain(mockDomRsrcCtx, "MetaDomProductid2"); + Integer productId2 = resDom.getYpmId(); + assertFalse(productId.intValue() == productId2.intValue()); + + meta = createDomainMetaObject("Test3 Domain", "NewOrg", + false, true, "12345", productId2); + try { + zmsImpl.putDomainMeta(mockDomRsrcCtx, "MetaDomProductid", auditRef, meta); + fail("bad request exc not thrown"); + } catch (ResourceException exc) { + assertEquals(400, exc.getCode()); + assertTrue(exc.getMessage().contains("is already assigned to domain")); + } + + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, "MetaDomProductid", auditRef); + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, "MetaDomProductid2", auditRef); + System.clearProperty(ZMSConsts.ZMS_PROP_PRODUCT_ID_SUPPORT); + } + + @Test + public void testPutDomainMetaDefaults() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MetaDom2", + null, null, adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Domain resDom1 = zms.getDomain(mockDomRsrcCtx, "MetaDom2"); + assertNotNull(resDom1); + assertNull(resDom1.getDescription()); + assertNull(resDom1.getOrg()); + assertTrue(resDom1.getEnabled()); + assertFalse(resDom1.getAuditEnabled()); + + DomainMeta meta = createDomainMetaObject("Test2 Domain", "NewOrg", + true, false, null, 0); + zms.putDomainMeta(mockDomRsrcCtx, "MetaDom2", auditRef, meta); + + Domain resDom3 = zms.getDomain(mockDomRsrcCtx, "MetaDom2"); + assertNotNull(resDom3); + assertEquals(resDom3.getDescription(), "Test2 Domain"); + assertEquals(resDom3.getOrg(), "NewOrg"); + assertTrue(resDom3.getEnabled()); + assertFalse(resDom3.getAuditEnabled()); + assertNull(resDom3.getAccount()); + assertEquals(Integer.valueOf(0), resDom3.getYpmId()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MetaDom2", auditRef); + } + + @Test + public void testPutDomainMetaMissingAuditRef() { + String domain = "testSetDomainMetaMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test1 Domain", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Domain resDom = zms.getDomain(mockDomRsrcCtx, domain); + assertNotNull(resDom); + assertEquals(resDom.getDescription(), "Test1 Domain"); + assertEquals(resDom.getOrg(), "testOrg"); + assertTrue(resDom.getAuditEnabled()); + + DomainMeta meta = createDomainMetaObject("Test2 Domain", "NewOrg", false, true, null, 0); + try { + zms.putDomainMeta(mockDomRsrcCtx, domain, null, meta); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test(groups="post-domain-tests") + public void testPutDomainMetaSubDomain() { + try { + TopLevelDomain dom = createTopLevelDomainObject("MetaDomProductid", + "Test Domain", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + } catch (ResourceException rexc) { + assertTrue(rexc.getCode() == 400); + } + + SubDomain subDom = createSubDomainObject("metaSubDom", "MetaDomProductid", + "sub Domain", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "MetaDomProductid", auditRef, subDom); + + // put meta data with null productId + DomainMeta meta = createDomainMetaObject("Test sub Domain", "NewOrg", + false, true, "12345", null); + zms.putDomainMeta(mockDomRsrcCtx, "MetaDomProductid.metaSubDom", auditRef, meta); + + // put meta data with a productId + meta = createDomainMetaObject("Test sub Domain", "NewOrg", + false, true, "12345", getRandomProductId()); + zms.putDomainMeta(mockDomRsrcCtx, "MetaDomProductid.metaSubDom", auditRef, meta); + + zms.deleteSubDomain(mockDomRsrcCtx, "MetaDomProductid", "metaSubDom", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MetaDomProductid", auditRef); + } + + @Test + public void testGetRoleList() { + + TopLevelDomain dom1 = createTopLevelDomainObject("RoleListDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("RoleListDom1", "Role1", null, "user.joe", + "user.jane"); + zms.putRole(mockDomRsrcCtx, "RoleListDom1", "Role1", auditRef, role1); + + Role role2 = createRoleObject("RoleListDom1", "Role2", null, "user.joe", + "user.jane"); + zms.putRole(mockDomRsrcCtx, "RoleListDom1", "Role2", auditRef, role2); + + RoleList roleList = zms.getRoleList(mockDomRsrcCtx, "RoleListDom1", null, null); + assertNotNull(roleList); + + assertTrue(roleList.getNames().contains("Role1".toLowerCase())); + assertTrue(roleList.getNames().contains("Role2".toLowerCase())); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "RoleListDom1", auditRef); + } + + @Test + public void testGetRoleListParams() { + + TopLevelDomain dom1 = createTopLevelDomainObject("RoleListParamDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("RoleListParamDom1", "Role1", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "RoleListParamDom1", "Role1", auditRef, role1); + + Role role2 = createRoleObject("RoleListParamDom1", "Role2", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "RoleListParamDom1", "Role2", auditRef, role2); + + RoleList roleList = zms.getRoleList(mockDomRsrcCtx, "RoleListParamDom1", null, "Role1"); + assertNotNull(roleList); + + assertFalse(roleList.getNames().contains("Role1".toLowerCase())); + assertTrue(roleList.getNames().contains("Role2".toLowerCase())); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "RoleListParamDom1", auditRef); + } + + @Test + public void testGetRoleListThrowException() { + try { + zms.getRoleList(mockDomRsrcCtx, "wrongDomainName", null, null); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + } + + @Test + public void testGetRole() { + + TopLevelDomain dom1 = createTopLevelDomainObject("GetRoleDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("GetRoleDom1", "Role1", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "GetRoleDom1", "Role1", auditRef, role1); + + Role role = zms.getRole(mockDomRsrcCtx, "GetRoleDom1", "Role1", false, false); + assertNotNull(role); + + assertEquals(role.getName(), "GetRoleDom1:role.Role1".toLowerCase()); + assertNull(role.getTrust()); + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 2); + + assertTrue(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "GetRoleDom1", auditRef); + } + + @Test + public void testGetRoleThrowException() { + String domainName = "MbrGetRoleDom1"; + String roleName = "Role1"; + + // Tests the getRole() condition: if (domain == null)... + try { + zms.getRole(mockDomRsrcCtx, domainName, roleName, false, false); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // Tests the getRole() condition: if (collection == null)... + try { + // Should fail because we did not create a role resource. + zms.getRole(mockDomRsrcCtx, domainName, roleName, false, false); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + // Tests the getRole() condition: if (role == null)... + String wrongRoleName = "Role2"; + try { + Role role1 = createRoleObject(domainName, roleName, null); + zms.putRole(mockDomRsrcCtx, domainName, roleName, auditRef, role1); + + // Should fail because we are trying to find a non-existent role. + zms.getRole(mockDomRsrcCtx, domainName, wrongRoleName, false, false); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testPutRoleThrowException() { + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_putrolethrowexc"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String domainName = "DomainName1"; + String roleName = "RoleName1"; + Role role = new Role(); + + // Tests the getRole() condition : if (!roleResourceName(domainName, roleName).equals(role.getName()))... + try { + String roleRoleName = "inconsistentRoleName1"; + role.setName(roleRoleName); + + // The role naming is inconsistent. + zmsImpl.putRole(mockDomRsrcCtx, domainName, roleName, auditRef, role); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } + boolean foundError = false; + for (String msg: aLogMsgs) { + if (!msg.contains("WHAT-api=(putrole)")) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.contains("WHAT-details=(ERROR=(putRole: Inconsistent role names - expected: domainname1:role.rolename1, actual: inconsistentrolename1))")); + foundError = true; + break; + } + assertTrue(foundError); + + aLogMsgs.clear(); + // Tests the getRole() condition : if (domain == null)... + try { + String roleRoleName = "DomainName1:role.RoleName1"; + role.setName(roleRoleName); + + // We never created a domain for this role. + zmsImpl.putRole(mockDomRsrcCtx, domainName, roleName, auditRef, role); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(putrole)") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(putrole: Unknown domain: domainname1))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + + FileConnection.deleteDirectory(new File(storeDir)); + } + + @Test + public void testCreateRole() { + + TopLevelDomain dom1 = createTopLevelDomainObject("CreateRoleDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("CreateRoleDom1", "Role1", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "CreateRoleDom1", "Role1", auditRef, role1); + + Role role3 = zms.getRole(mockDomRsrcCtx, "CreateRoleDom1", "Role1", false, false); + assertNotNull(role3); + assertEquals(role3.getName(), "CreateRoleDom1:role.Role1".toLowerCase()); + assertNull(role3.getTrust()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "CreateRoleDom1", auditRef); + } + + @Test + public void testCreateRoleLocalNameOnly() { + + TopLevelDomain dom1 = createTopLevelDomainObject("CreateRoleDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = new Role(); + role1.setName("role1"); + + zms.putRole(mockDomRsrcCtx, "CreateRoleDom1", "Role1", auditRef, role1); + + Role role3 = zms.getRole(mockDomRsrcCtx, "CreateRoleDom1", "Role1", false, false); + assertNotNull(role3); + assertEquals(role3.getName(), "CreateRoleDom1:role.Role1".toLowerCase()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "CreateRoleDom1", auditRef); + } + + @Test + public void testCreateRoleMissingAuditRef() { + String domain = "testCreateRoleMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Role role = createRoleObject( + domain, "Role1", null, "user.joe", "user.jane"); + try { + zms.putRole(mockDomRsrcCtx, domain, "Role1", null, role); + fail("requesterror not thrown by putRole."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testCreateRoleMismatchName() { + + TopLevelDomain dom1 = createTopLevelDomainObject( + "CreateMismatchRoleDom1", "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("CreateMismatchRoleDom1", "Role1", null, + "user.joe", "user.jane"); + + try { + zms.putRole(mockDomRsrcCtx, "CreateMismatchRoleDom1", + "CreateMismatchRoleDom1.Role1", auditRef, role1); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "CreateMismatchRoleDom1", auditRef); + } + + @Test + public void testCreateRoleInvalidName() { + + TopLevelDomain dom1 = createTopLevelDomainObject( + "CreateRoleInvalidNameDom1", "Test Domain1", "testOrg", + adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = new Role(); + role1.setName("Role1"); + + try { + zms.putRole(mockDomRsrcCtx, "CreateRoleInvalidNameDom1", "Role111", auditRef, role1); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx,"CreateRoleInvalidNameDom1", auditRef); + } + + @Test + public void testCreateRoleInvalidStruct() { + + TopLevelDomain dom1 = createTopLevelDomainObject( + "CreateRoleInvalidStructDom1", "Test Domain1", "testOrg", + adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = new Role(); + + try { + zms.putRole(mockDomRsrcCtx, "CreateRoleInvalidStructDom1", "Role1", auditRef, role1); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx,"CreateRoleInvalidStructDom1", auditRef); + } + + @Test + public void testCreateRoleInvalidMembers() { + + TopLevelDomain dom1 = createTopLevelDomainObject( + "CreateInvalidMemberRoleDom1", "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("CreateInvalidMemberRoleDom1", "Role1", null, + "user.joe", "jane"); + + try { + zms.putRole(mockDomRsrcCtx, "CreateInvalidMemberRoleDom1", "Role1", auditRef, role1); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + Role role2 = createRoleObject("CreateInvalidMemberRoleDom1", "Role2", null, + "joe", "user.jane"); + + try { + zms.putRole(mockDomRsrcCtx, "CreateInvalidMemberRoleDom1", "Role2", auditRef, role2); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx,"CreateInvalidMemberRoleDom1", auditRef); + } + + @Test + public void testCreateRoleBothMemberAndTrust() { + + String domainName = "rolebothmemberandtrust"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject(domainName, "Role1", "sys.auth", + "user.joe", "user.jane"); + + try { + zms.putRole(mockDomRsrcCtx, domainName, "Role1", auditRef, role1); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testCreateRoleTrustItself() { + + String domainName = "roletrustitself"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject(domainName, "Role1", domainName, + null, null); + + try { + zms.putRole(mockDomRsrcCtx, domainName, "Role1", auditRef, role1); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testCreateDuplicateMemberRole() { + + TopLevelDomain dom1 = createTopLevelDomainObject("CreateDuplicateMemberRoleDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("CreateDuplicateMemberRoleDom1", "Role1", null, + "user.joe", "user.joe"); + zms.putRole(mockDomRsrcCtx, "CreateDuplicateMemberRoleDom1", "Role1", auditRef, role1); + + Role role = zms.getRole(mockDomRsrcCtx, "CreateDuplicateMemberRoleDom1", "Role1", false, false); + assertNotNull(role); + + assertEquals(role.getName(), "CreateDuplicateMemberRoleDom1:role.Role1".toLowerCase()); + assertNull(role.getTrust()); + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 1); + assertTrue(members.get(0).equals("user.joe")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx,"CreateDuplicateMemberRoleDom1", auditRef); + } + + @Test + public void testCreateNormalizedUserMemberRole() { + + TopLevelDomain dom1 = createTopLevelDomainObject("CreateNormalizedUserMemberRoleDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ArrayList roleMembers = new ArrayList<>(); + roleMembers.add("user:joe"); + roleMembers.add("user.joe"); + roleMembers.add("user:joe"); + roleMembers.add("user.jane"); + + Role role1 = createRoleObject("CreateNormalizedUserMemberRoleDom1", "Role1", + null, roleMembers); + zms.putRole(mockDomRsrcCtx, "CreateNormalizedUserMemberRoleDom1", "Role1", auditRef, role1); + + Role role = zms.getRole(mockDomRsrcCtx, "CreateNormalizedUserMemberRoleDom1", "Role1", false, false); + assertNotNull(role); + + assertEquals(role.getName(), "CreateNormalizedUserMemberRoleDom1:role.Role1".toLowerCase()); + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 2); + assertTrue(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx,"CreateNormalizedUserMemberRoleDom1", auditRef); + } + + @Test + public void testCreateNormalizedServiceMemberRole() { + + TopLevelDomain dom1 = createTopLevelDomainObject("CreateNormalizedServiceMemberRoleDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("coretech", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + SubDomain subDom2 = createSubDomainObject("storage", "coretech", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "coretech", auditRef, subDom2); + + SubDomain subDom3 = createSubDomainObject("user1", "user", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "user", auditRef, subDom3); + + SubDomain subDom4 = createSubDomainObject("dom1", "user.user1", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, subDom4); + + ArrayList roleMembers = new ArrayList<>(); + roleMembers.add("coretech.storage"); + roleMembers.add("coretech:service.storage"); + roleMembers.add("user.user1.dom1:service.api"); + + Role role1 = createRoleObject("CreateNormalizedServiceMemberRoleDom1", "Role1", + null, roleMembers); + zms.putRole(mockDomRsrcCtx, "CreateNormalizedServiceMemberRoleDom1", "Role1", auditRef, role1); + + Role role = zms.getRole(mockDomRsrcCtx, "CreateNormalizedServiceMemberRoleDom1", "Role1", false, false); + assertNotNull(role); + + assertEquals(role.getName(), "CreateNormalizedServiceMemberRoleDom1:role.Role1".toLowerCase()); + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 2); + assertTrue(members.contains("coretech.storage")); + assertTrue(members.contains("user.user1.dom1.api")); + + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "dom1", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user", "user1", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "coretech", "storage", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx,"CreateNormalizedServiceMemberRoleDom1", auditRef); + } + + @Test + public void testCreateNormalizedCombinedMemberRole() { + + TopLevelDomain dom1 = createTopLevelDomainObject("CreateNormalizedCombinedMemberRoleDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("coretech", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + SubDomain subDom2 = createSubDomainObject("storage", "coretech", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "coretech", auditRef, subDom2); + + SubDomain subDom3 = createSubDomainObject("user1", "user", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "user", auditRef, subDom3); + + SubDomain subDom4 = createSubDomainObject("dom1", "user.user1", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, subDom4); + + ArrayList roleMembers = new ArrayList<>(); + roleMembers.add("user:joe"); + roleMembers.add("user.joe"); + roleMembers.add("user:joe"); + roleMembers.add("user.jane"); + roleMembers.add("coretech.storage"); + roleMembers.add("coretech:service.storage"); + roleMembers.add("user.user1.dom1:service.api"); + + Role role1 = createRoleObject("CreateNormalizedCombinedMemberRoleDom1", "Role1", + null, roleMembers); + zms.putRole(mockDomRsrcCtx, "CreateNormalizedCombinedMemberRoleDom1", "Role1", auditRef, role1); + + Role role = zms.getRole(mockDomRsrcCtx, "CreateNormalizedCombinedMemberRoleDom1", "Role1", false, false); + assertNotNull(role); + + assertEquals(role.getName(), "CreateNormalizedCombinedMemberRoleDom1:role.Role1".toLowerCase()); + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 4); + assertTrue(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + assertTrue(members.contains("coretech.storage")); + assertTrue(members.contains("user.user1.dom1.api")); + + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "dom1", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user", "user1", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "coretech", "storage", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx,"CreateNormalizedCombinedMemberRoleDom1", auditRef); + } + + @Test + public void testDeleteRole() { + + TopLevelDomain dom1 = createTopLevelDomainObject("DelRoleDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("DelRoleDom1", "Role1", null, "user.joe", + "user.jane"); + zms.putRole(mockDomRsrcCtx, "DelRoleDom1", "Role1", auditRef, role1); + + Role role2 = createRoleObject("DelRoleDom1", "Role2", null, "user.joe", + "user.jane"); + zms.putRole(mockDomRsrcCtx, "DelRoleDom1", "Role2", auditRef, role2); + + RoleList roleList = zms.getRoleList(mockDomRsrcCtx, "DelRoleDom1", null, null); + assertNotNull(roleList); + + // our role count is +1 because of the admin role + assertEquals(roleList.getNames().size(), 3); + + zms.deleteRole(mockDomRsrcCtx,"DelRoleDom1", "Role1", auditRef); + + roleList = zms.getRoleList(mockDomRsrcCtx, "DelRoleDom1", null, null); + assertNotNull(roleList); + + // our role counti is +1 because of the admin role + assertEquals(roleList.getNames().size(), 2); + + assertFalse(roleList.getNames().contains("Role1".toLowerCase())); + assertTrue(roleList.getNames().contains("Role2".toLowerCase())); + + zms.deleteTopLevelDomain(mockDomRsrcCtx,"DelRoleDom1", auditRef); + } + + @Test + public void testDeleteRoleMissingAuditRef() { + String domain = "testDeleteRoleMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Role role = createRoleObject( + domain, "Role1", null, "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, domain, "Role1", auditRef, role); + + try { + zms.deleteRole(mockDomRsrcCtx, domain, "Role1", null); + fail("requesterror not thrown by deleteRole."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testDeleteRoleThrowException() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_delrolethrowexc"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String caller = "deleterole"; + String domainName = "DomainName1"; + String roleName = "RoleName1"; + try { + zmsImpl.deleteRole(mockDomRsrcCtx,domainName, roleName, auditRef); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + FileConnection.deleteDirectory(new File(storeDir)); + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(deleterole: Unknown domain: domainname1))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + } + + @Test + public void testDeleteAdminRole() { + + TopLevelDomain dom1 = createTopLevelDomainObject("DelAdminRoleDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + try { + zms.deleteRole(mockDomRsrcCtx,"DelAdminRoleDom1", "admin", auditRef); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx,"DelAdminRoleDom1", auditRef); + } + + @Test + public void testGetMembership() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrGetRoleDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("MbrGetRoleDom1", "Role1", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrGetRoleDom1", "Role1", auditRef, role1); + + Membership member1 = zms.getMembership(mockDomRsrcCtx, "MbrGetRoleDom1", "Role1", + "user.joe"); + assertNotNull(member1); + assertEquals(member1.getMemberName(), "user.joe"); + assertEquals(member1.getRoleName(), "MbrGetRoleDom1:role.Role1".toLowerCase()); + assertTrue(member1.getIsMember()); + + Membership member2 = zms.getMembership(mockDomRsrcCtx, "MbrGetRoleDom1", "Role1", + "user.doe"); + assertNotNull(member2); + assertEquals(member2.getMemberName(), "user.doe"); + assertEquals(member2.getRoleName(), "MbrGetRoleDom1:role.Role1".toLowerCase()); + assertFalse(member2.getIsMember()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx,"MbrGetRoleDom1", auditRef); + } + + @Test + public void testGetMembershipThrowException() { + String domainName = "MbrGetRoleDom1"; + String roleName = "Role1"; + String memberName1 = "user.john"; + String memberName2 = "user.jane"; + + // Tests the getMembership() condition : if (domain == null)... + try { + // Should fail because we never created this domain. + zms.getMembership(mockDomRsrcCtx, domainName, roleName, memberName1); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // Tests the getMembership() condition: if (collection == null)... + try { + // Should fail because we never added a role to this domain. + zms.getMembership(mockDomRsrcCtx, domainName, roleName, memberName1); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + // Tests the getMembership() condition: if (role == null)... + try { + String missingRoleName = "Role2"; + + Role role1 = createRoleObject("MbrGetRoleDom1", "Role1", null, + memberName1, memberName2); + zms.putRole(mockDomRsrcCtx, domainName, roleName, auditRef, role1); + + // Trying to find a non-existent role. + zms.getMembership(mockDomRsrcCtx, domainName, missingRoleName, memberName1); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx,domainName, auditRef); + } + + @Test + public void testPutMembership() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrAddDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("coretech", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + SubDomain subDom2 = createSubDomainObject("storage", "coretech", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "coretech", auditRef, subDom2); + + Role role1 = createRoleObject("MbrAddDom1", "Role1", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrAddDom1", "Role1", auditRef, role1); + + Membership mbr = generateMembership("Role1", "user.doe"); + zms.putMembership(mockDomRsrcCtx, "MbrAddDom1", "Role1", "user.doe", auditRef, mbr); + + mbr = generateMembership("Role1", "coretech.storage"); + zms.putMembership(mockDomRsrcCtx, "MbrAddDom1", "Role1", "coretech.storage", auditRef, mbr); + + Role role = zms.getRole(mockDomRsrcCtx, "MbrAddDom1", "Role1", false, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 4); + + assertTrue(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + assertTrue(members.contains("user.doe")); + assertTrue(members.contains("coretech.storage")); + + zms.deleteSubDomain(mockDomRsrcCtx, "coretech", "storage", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx,"MbrAddDom1", auditRef); + } + + @Test + public void testPutMembershipEmptyRoleMembers() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrAddDom1EmptyRole", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = new Role(); + role1.setName(ZMSUtils.roleResourceName("MbrAddDom1EmptyRole", "Role1")); + zms.putRole(mockDomRsrcCtx, "MbrAddDom1EmptyRole", "Role1", auditRef, role1); + + Membership mbr = generateMembership("Role1", "user.doe"); + zms.putMembership(mockDomRsrcCtx, "MbrAddDom1EmptyRole", "Role1", "user.doe", auditRef, mbr); + + Role role = zms.getRole(mockDomRsrcCtx, "MbrAddDom1EmptyRole", "Role1", false, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 1); + + assertTrue(members.contains("user.doe")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx,"MbrAddDom1EmptyRole", auditRef); + } + + @Test + public void testPutMembershipMissingAuditRef() { + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_putmembershipmissauditref"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String domain = "testPutMembershipMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Role role = createRoleObject( + domain, "Role1", null, "user.joe", "user.jane"); + zmsImpl.putRole(mockDomRsrcCtx, domain, "Role1", auditRef, role); + + Membership mbr = generateMembership("Role1", "user.john"); + try { + zmsImpl.putMembership(mockDomRsrcCtx, domain, "Role1", "user.john", null, mbr); + fail("requesterror not thrown by putMembership."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + + String caller = "putmembership"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(putmembership: Audit reference required for domain: testputmembershipmissingauditref);:caller specified memberName=(user.john))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + } + + @Test + public void testPutMembershipNormalizedUser() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrAddDom2", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("coretech", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + SubDomain subDom2 = createSubDomainObject("storage", "coretech", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "coretech", auditRef, subDom2); + + Role role1 = createRoleObject("MbrAddDom2", "Role1", null, + "coretech.storage", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrAddDom2", "Role1", auditRef, role1); + + Membership mbr = generateMembership("Role1", "user:doe"); + zms.putMembership(mockDomRsrcCtx, "MbrAddDom2", "Role1", "user:doe", auditRef, mbr); + + Role role = zms.getRole(mockDomRsrcCtx, "MbrAddDom2", "Role1", false, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 3); + + assertTrue(members.contains("coretech.storage")); + assertTrue(members.contains("user.jane")); + assertTrue(members.contains("user.doe")); + + zms.deleteSubDomain(mockDomRsrcCtx, "coretech", "storage", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MbrAddDom2", auditRef); + } + + @Test + public void testPutMembershipNormalizedUseruser() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrAddDom3", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("coretech", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + SubDomain subDom2 = createSubDomainObject("storage", "coretech", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "coretech", auditRef, subDom2); + + Role role1 = createRoleObject("MbrAddDom3", "Role1", null, + "coretech.storage", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrAddDom3", "Role1", auditRef, role1); + + Membership mbr = generateMembership("Role1", "user:doe"); + zms.putMembership(mockDomRsrcCtx, "MbrAddDom3", "Role1", "user:doe", auditRef, mbr); + + Role role = zms.getRole(mockDomRsrcCtx, "MbrAddDom3", "Role1", false, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 3); + + assertTrue(members.contains("coretech.storage")); + assertTrue(members.contains("user.jane")); + assertTrue(members.contains("user.doe")); + + zms.deleteSubDomain(mockDomRsrcCtx, "coretech", "storage", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MbrAddDom3", auditRef); + } + + @Test + public void testPutMembershipNormalizedService() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrAddDom4", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("coretech", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + SubDomain subDom2 = createSubDomainObject("storage", "coretech", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "coretech", auditRef, subDom2); + + TopLevelDomain dom3 = createTopLevelDomainObject("weather", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom3); + + SubDomain subDom3 = createSubDomainObject("storage", "weather", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "weather", auditRef, subDom3); + + Role role1 = createRoleObject("MbrAddDom4", "Role1", null, + "coretech.storage", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrAddDom4", "Role1", auditRef, role1); + + Membership mbr = generateMembership("Role1", "weather:service.storage"); + zms.putMembership(mockDomRsrcCtx, "MbrAddDom4", "Role1", "weather:service.storage", auditRef, mbr); + + Role role = zms.getRole(mockDomRsrcCtx, "MbrAddDom4", "Role1", false, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 3); + + assertTrue(members.contains("coretech.storage")); + assertTrue(members.contains("user.jane")); + assertTrue(members.contains("weather.storage")); + + zms.deleteSubDomain(mockDomRsrcCtx, "weather", "storage", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "weather", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "coretech", "storage", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MbrAddDom4", auditRef); + } + + public void testPutMembershipRoleNotPresent() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrAddDomNoRole", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("coretech", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + SubDomain subDom2 = createSubDomainObject("storage", "coretech", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "coretech", auditRef, subDom2); + + Role role1 = createRoleObject("MbrAddDomNoRole", "Role1", null, + "coretech.storage", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrAddDomNoRole", "Role1", auditRef, role1); + + // membership object with only member + + Membership mbr = new Membership(); + mbr.setMemberName("user.joe"); + + zms.putMembership(mockDomRsrcCtx, "MbrAddDomNoRole", "Role1", "user.joe", auditRef, mbr); + + Role role = zms.getRole(mockDomRsrcCtx, "MbrAddDomNoRole", "Role1", false, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 3); + + assertTrue(members.contains("coretech.storage")); + assertTrue(members.contains("user.jane")); + assertTrue(members.contains("user.joe")); + + zms.deleteSubDomain(mockDomRsrcCtx, "coretech", "storage", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MbrAddDomNoRole", auditRef); + } + + @Test + public void testPutMembershipInvalid() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrAddDom5", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("MbrAddDom5", "Role1", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrAddDom5", "Role1", auditRef, role1); + try { + Membership mbr = generateMembership("Role1", "coretech"); + zms.putMembership(mockDomRsrcCtx, "MbrAddDom5", "Role1", "coretech", auditRef, mbr); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MbrAddDom5", auditRef); + } + + @Test + public void testPutMembershipRoleMismatch() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrAddDom6", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("MbrAddDom6", "Role1", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrAddDom6", "Role1", auditRef, role1); + try { + Membership mbr = generateMembership("Role2", "user.john"); + zms.putMembership(mockDomRsrcCtx, "MbrAddDom6", "Role1", "user.john", auditRef, mbr); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MbrAddDom6", auditRef); + } + + @Test + public void testPutMembershipMemberMismatch() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrAddDom7", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("MbrAddDom7", "Role1", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrAddDom7", "Role1", auditRef, role1); + try { + Membership mbr = generateMembership("Role1", "user.john"); + zms.putMembership(mockDomRsrcCtx, "MbrAddDom7", "Role1", "user.johnny", auditRef, mbr); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MbrAddDom7", auditRef); + } + + @Test + public void testPutMembershipThrowException() { + String domainName = "MbrGetRoleDom1"; + String roleName = "Role1"; + String memberName1 = "user.john"; + String memberName2 = "user.jane"; + String wrongDomainName = "MbrGetRoleDom2"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // Tests the putMembership() condition : if (domain == null)... + try { + // Trying to add a wrong domain name. + Membership mbr = generateMembership(roleName, memberName1); + zms.putMembership(mockDomRsrcCtx, wrongDomainName, roleName, memberName1, auditRef, mbr); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + // Tests the putMembership() condition: if (collection == null)... + try { + // Should fail because we never added a role resource. + Membership mbr = generateMembership(roleName, memberName1); + zms.putMembership(mockDomRsrcCtx, domainName, roleName, memberName1, auditRef, mbr); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + // Tests the putMembership() condition: if (role == null)... + try { + String wrongRoleName = "Role2"; + + Role role1 = createRoleObject(domainName, roleName, null, + memberName1, memberName2); + zms.putRole(mockDomRsrcCtx, domainName, roleName, auditRef, role1); + + // Trying to add member to non-existent role. + Membership mbr = generateMembership(wrongRoleName, memberName1); + zms.putMembership(mockDomRsrcCtx, domainName, wrongRoleName, memberName1, auditRef, mbr); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testDeleteMembership() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrDelDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("MbrDelDom1", "Role1", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrDelDom1", "Role1", auditRef, role1); + zms.deleteMembership(mockDomRsrcCtx, "MbrDelDom1", "Role1", "user.joe", auditRef); + + Role role = zms.getRole(mockDomRsrcCtx, "MbrDelDom1", "Role1", false, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 1); + + assertFalse(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MbrDelDom1", auditRef); + } + + @Test + public void testDeleteMembershipMissingAuditRef() { + String domain = "testDeleteMembershipMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Role role = createRoleObject( + domain, "Role1", null, "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, domain, "Role1", auditRef, role); + + try { + zms.deleteMembership(mockDomRsrcCtx, domain, "Role1", "user.joe", null); + fail("requesterror not thrown by deleteMembership."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testDeleteMembershipInvalidDomain() { + String domainName = "MbrGetRoleDom1"; + String roleName = "Role1"; + String memberName1 = "user.john"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // Tests the deleteMembership() condition : if (domain == null)... + try { + String wrongDomainName = "MbrGetRoleDom2"; + + // Should fail because this domain does not exist. + zms.deleteMembership(mockDomRsrcCtx, wrongDomainName, roleName, memberName1, auditRef); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testDeleteMembershipInvalidRoleCollection() { + String domainName = "MbrGetRoleDom1"; + String roleName = "Role1"; + String memberName1 = "user.john"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // Test the deleteMembership() condition: if (collection == null)... + try { + // Should fail b/c a role entity was never added. + zms.deleteMembership(mockDomRsrcCtx, domainName, roleName, memberName1, auditRef); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testDeleteMembershipInvalidRole() { + String domainName = "MbrGetRoleDom1"; + String roleName = "Role1"; + String memberName1 = "user.john"; + String memberName2 = "user.jane"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // Tests the deleteMembership() condition: if (role == null)... + try { + String wrongRoleName = "Role2"; + Role role1 = createRoleObject(domainName, roleName, null, + memberName1, memberName2); + zms.putRole(mockDomRsrcCtx, domainName, roleName, auditRef, role1); + + // Should fail b/c trying to find a non-existent role. + zms.deleteMembership(mockDomRsrcCtx, domainName, wrongRoleName, memberName1, auditRef); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testDeleteMembershipAdminRoleSingleMember() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_delmembershipadminrsm"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String caller = "deletemembership"; + String domainName = "MbrGetRoleDom1"; + String memberName1 = "user.john"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // Test the deleteMembership() condition: if ("admin".equals(roleName))... + try { + String adminRoleName = "admin"; + + List members = new ArrayList(); + members.add(memberName1); + Role role1 = createRoleObject(domainName, adminRoleName, null, members); + zmsImpl.putRole(mockDomRsrcCtx, domainName, adminRoleName, auditRef, role1); + + // Can not delete the last admin role. + zmsImpl.deleteMembership(mockDomRsrcCtx, domainName, adminRoleName, memberName1, auditRef); + fail("forbiddenerror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 403); + } + + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(deletemembership: Cannot delete last member of 'admin' role);:caller specified memberName=(user.john))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testDeleteMembershipNormalizedUser() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrDelDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("MbrDelDom1", "Role1", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrDelDom1", "Role1", auditRef, role1); + zms.deleteMembership(mockDomRsrcCtx, "MbrDelDom1", "Role1", "user:joe", auditRef); + + Role role = zms.getRole(mockDomRsrcCtx, "MbrDelDom1", "Role1", false, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 1); + + assertFalse(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MbrDelDom1", auditRef); + } + + @Test + public void testDeleteMembershipNormalizeduser() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrDelDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("MbrDelDom1", "Role1", null, + "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, "MbrDelDom1", "Role1", auditRef, role1); + zms.deleteMembership(mockDomRsrcCtx, "MbrDelDom1", "Role1", "user:joe", auditRef); + + Role role = zms.getRole(mockDomRsrcCtx, "MbrDelDom1", "Role1", false, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 1); + + assertFalse(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MbrDelDom1", auditRef); + } + + @Test + public void testDeleteMembershipNormalizedService() { + + TopLevelDomain dom1 = createTopLevelDomainObject("MbrDelDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("coretech", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + SubDomain subDom2 = createSubDomainObject("storage", "coretech", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "coretech", auditRef, subDom2); + + Role role1 = createRoleObject("MbrDelDom1", "Role1", null, + "user.joe", "coretech.storage"); + zms.putRole(mockDomRsrcCtx, "MbrDelDom1", "Role1", auditRef, role1); + zms.deleteMembership(mockDomRsrcCtx, "MbrDelDom1", "Role1", "coretech:service.storage", auditRef); + + Role role = zms.getRole(mockDomRsrcCtx, "MbrDelDom1", "Role1", false, false); + assertNotNull(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 1); + + assertTrue(members.contains("user.joe")); + + zms.deleteSubDomain(mockDomRsrcCtx, "coretech", "storage", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "MbrDelDom1", auditRef); + } + + @Test + public void testGetPolicyList() { + + TopLevelDomain dom1 = createTopLevelDomainObject("PolicyListDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject("PolicyListDom1", "Policy1"); + zms.putPolicy(mockDomRsrcCtx, "PolicyListDom1", "Policy1", auditRef, policy1); + + Policy policy2 = createPolicyObject("PolicyListDom1", "Policy2"); + zms.putPolicy(mockDomRsrcCtx, "PolicyListDom1", "Policy2", auditRef, policy2); + + PolicyList policyList = zms.getPolicyList(mockDomRsrcCtx, "PolicyListDom1", null, null); + assertNotNull(policyList); + + // policy count +1 due to admin policy + assertEquals(policyList.getNames().size(), 3); + + assertTrue(policyList.getNames().contains("Policy1".toLowerCase())); + assertTrue(policyList.getNames().contains("Policy2".toLowerCase())); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "PolicyListDom1", auditRef); + } + + @Test + public void testGetPolicyListParams() { + + TopLevelDomain dom1 = createTopLevelDomainObject( + "PolicyListParamsDom1", "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject("PolicyListParamsDom1", "Policy1"); + zms.putPolicy(mockDomRsrcCtx, "PolicyListParamsDom1", "Policy1", auditRef, policy1); + + Policy policy2 = createPolicyObject("PolicyListParamsDom1", "Policy2"); + zms.putPolicy(mockDomRsrcCtx, "PolicyListParamsDom1", "Policy2", auditRef, policy2); + + PolicyList policyList = zms.getPolicyList(mockDomRsrcCtx, "PolicyListParamsDom1", null, + "Policy1"); + assertNotNull(policyList); + + assertFalse(policyList.getNames().contains("Policy1".toLowerCase())); + assertTrue(policyList.getNames().contains("Policy2".toLowerCase())); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "PolicyListParamsDom1", auditRef); + } + + @Test + public void testGetPolicyListThrowException() { + try { + zms.getPolicyList(mockDomRsrcCtx, "WrongDomainName", null, null); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + } + + @Test + public void testGetPolicy() { + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_getpol"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + TopLevelDomain dom1 = createTopLevelDomainObject("PolicyGetDom1", + "Test Domain1", "testOrg", adminUser); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject("PolicyGetDom1", "Policy1"); + zmsImpl.putPolicy(mockDomRsrcCtx, "PolicyGetDom1", "Policy1", auditRef, policy1); + + Policy policy = zmsImpl.getPolicy(mockDomRsrcCtx, "PolicyGetDom1", "Policy1"); + assertNotNull(policy); + assertEquals(policy.getName(), "PolicyGetDom1:policy.Policy1".toLowerCase()); + + List assertList = policy.getAssertions(); + assertNotNull(assertList); + assertEquals(assertList.size(), 1); + Assertion obj = assertList.get(0); + assertEquals(obj.getAction(), "*"); + assertEquals(obj.getEffect(), AssertionEffect.ALLOW); + assertEquals(obj.getResource(), "policygetdom1:*"); + assertEquals(obj.getRole(), "PolicyGetDom1:role.Role1".toLowerCase()); + + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(putpolicy)") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + int index = msg.indexOf("WHAT-details=("); + assertTrue(msg, index != -1); + int index2 = msg.indexOf("role: \"policygetdom1:role.role1\""); + assertTrue(msg, index < index2); + index2 = msg.indexOf("ERROR"); + assertTrue(msg, index2 == -1); + foundError = true; + break; + } + assertTrue(foundError); + + // this should throw an exception + try { + zmsImpl.getPolicy(mockDomRsrcCtx, "PolicyGetDom1", "Policy2"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, "PolicyGetDom1", auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + + @Test + public void testGetPolicyThrowException() { + String domainName = "PolicyGetDom1"; + String policyName = "Policy1"; + + // Tests the getPolicy() condition : if (domain == null)... + try { + zms.getPolicy(mockDomRsrcCtx, domainName, policyName); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // Tests the getPolicy() condition: if (collection == null)... + try { + // Should fail b/c a policy was never added. + zms.getPolicy(mockDomRsrcCtx, domainName, policyName); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + // Tests the getPolicy() condition: if (policy == null)... + try { + String wrongPolicyName = "Policy2"; + + Policy policy1 = createPolicyObject(domainName, policyName); + zms.putPolicy(mockDomRsrcCtx, domainName, policyName, auditRef, policy1); + + // Should fail b/c trying to find a non-existent policy. + zms.getPolicy(mockDomRsrcCtx, domainName, wrongPolicyName); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testPutPolicyThrowException() { + String domainName = "DomainName"; + String policyName = "PolicyName"; + String wrongPolicyName = "WrongPolicyName"; + + // Tests the putPolicy() condition : if (!policyResourceName(domainName, policyName).equals(policy.getName()))... + try { + Policy policy = createPolicyObject(domainName, wrongPolicyName); + + // policyName should not be the same as policy.getName() + zms.putPolicy(mockDomRsrcCtx, domainName, policyName, auditRef, policy); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } + + // Tests the putPolicy() condition: if (domain == null)... + try { + Policy policy = createPolicyObject(domainName, policyName); + + // should fail b/c we never created a top level domain. + zms.putPolicy(mockDomRsrcCtx, domainName, policyName, auditRef, policy); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + } + + @Test + public void testCreatePolicy() { + + TopLevelDomain dom1 = createTopLevelDomainObject("PolicyAddDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject("PolicyAddDom1", "Policy1"); + zms.putPolicy(mockDomRsrcCtx, "PolicyAddDom1", "Policy1", auditRef, policy1); + + Policy policyRes2 = zms.getPolicy(mockDomRsrcCtx, "PolicyAddDom1", "Policy1"); + assertNotNull(policyRes2); + assertEquals(policyRes2.getName(), "PolicyAddDom1:policy.Policy1".toLowerCase()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "PolicyAddDom1", auditRef); + } + + @Test + public void testCreatePolicyWithLocalName() { + + TopLevelDomain dom1 = createTopLevelDomainObject("PolicyAddDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = new Policy(); + policy.setName("policy1"); + + Assertion assertion = new Assertion(); + assertion.setAction("read"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("policyadddom1:*"); + assertion.setRole("policyadddom1:role.admin"); + + List assertList = new ArrayList(); + assertList.add(assertion); + + policy.setAssertions(assertList); + + zms.putPolicy(mockDomRsrcCtx, "PolicyAddDom1", "Policy1", auditRef, policy); + + Policy policyRes2 = zms.getPolicy(mockDomRsrcCtx, "PolicyAddDom1", "Policy1"); + assertNotNull(policyRes2); + assertEquals(policyRes2.getName(), "PolicyAddDom1:policy.Policy1".toLowerCase()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "PolicyAddDom1", auditRef); + } + + @Test + public void testCreatePolicyMissingAuditRef() { + String domain = "testCreatePolicyMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Policy policy = createPolicyObject(domain, "Policy1"); + try { + zms.putPolicy(mockDomRsrcCtx, domain, "Policy1", null, policy); + fail("requesterror not thrown by putPolicy."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testPutPolicyChanges() { + String domain = "PutPolicyChanges"; + String policyName = "Jobs"; + TopLevelDomain dom1 = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject(domain, policyName); + List origAsserts = policy1.getAssertions(); + + String userId = "hank"; + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + String unsignedCreds = "v=U1;d=user;n=" + userId; + Principal principal = SimplePrincipal.create("user", userId, unsignedCreds + ";s=signature", + 0, principalAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(unsignedCreds); + + ResourceContext rsrcCtx1 = createResourceContext(principal); + zms.putPolicy(rsrcCtx1, domain, policyName, auditRef, policy1); + + Policy policyRes1A = zms.getPolicy(mockDomRsrcCtx, domain, policyName); + List resAsserts = policyRes1A.getAssertions(); + + // check assertions are the same - should only be 1 + assertEquals(origAsserts.size(), resAsserts.size()); + + // now replace the old assertion with a new ones + // + Assertion assertionA = new Assertion(); + assertionA.setResource(domain + ":books"); + assertionA.setAction("READ"); + assertionA.setRole(domain + ":role.librarian"); + assertionA.setEffect(AssertionEffect.ALLOW); + + Assertion assertionB = new Assertion(); + assertionB.setResource(domain + ":jupiter"); + assertionB.setAction("TRAVEL"); + assertionB.setRole(domain + ":role.astronaut"); + assertionB.setEffect(AssertionEffect.ALLOW); + + List newAssertions = new ArrayList(); + newAssertions.add(assertionA); + newAssertions.add(assertionB); + + policyRes1A.setAssertions(newAssertions); + + zms.putPolicy(mockDomRsrcCtx, domain, policyName, auditRef, policyRes1A); + + Policy policyRes1B = zms.getPolicy(mockDomRsrcCtx, domain, policyName); + List resAssertsB = policyRes1B.getAssertions(); + + // check assertions are the same - should be 2 + assertEquals(newAssertions.size(), resAssertsB.size()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + + @Test + public void testPutAdminPolicyRejection() { + + String domain = "put-admin-rejection"; + + TopLevelDomain dom1 = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = createPolicyObject(domain, "admin"); + try { + zms.putPolicy(mockDomRsrcCtx, domain, "admin", auditRef, policy); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage(), ex.getMessage().contains("admin policy cannot be modified")); + } + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + + @Test + public void testCreatePolicyNoAssertions() { + + TopLevelDomain dom1 = createTopLevelDomainObject( + "testCreatePolicyNoAssertions", "Test Domain1", "testOrg", + adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = new Policy(); + policy1.setName(ZMSUtils.policyResourceName("testCreatePolicyNoAssertions", + "Policy1")); + + try { + zms.putPolicy(mockDomRsrcCtx, "testCreatePolicyNoAssertions", "Policy1", auditRef, policy1); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "testCreatePolicyNoAssertions", auditRef); + } + + @Test + public void testPutPolicyInvalidAssertionResources() { + + String domainName = "InvalidAssertionResources"; + String policyName = "Policy1"; + + TopLevelDomain dom1 = createTopLevelDomainObject( + domainName, "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = new Policy(); + policy.setName(ZMSUtils.policyResourceName(domainName, policyName)); + + // assertion missing domain name + + Assertion assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("resource1"); + assertion.setRole(ZMSUtils.roleResourceName(domainName, "role1")); + + List assertList = new ArrayList(); + assertList.add(assertion); + policy.setAssertions(assertList); + + try { + zms.putPolicy(mockDomRsrcCtx, domainName, policyName, auditRef, policy); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + // assertion with invalid domain name + + assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("domain name:resource1"); + assertion.setRole(ZMSUtils.roleResourceName(domainName, "role1")); + + assertList.clear(); + assertList.add(assertion); + policy.setAssertions(assertList); + + try { + zms.putPolicy(mockDomRsrcCtx, domainName, policyName, auditRef, policy); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testCreatePolicyMismatchName() { + + TopLevelDomain dom1 = createTopLevelDomainObject( + "PolicyAddMismatchNameDom1", "Test Domain1", "testOrg", + adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject("PolicyAddMismatchNameDom1", + "Policy1"); + + try { + zms.putPolicy(mockDomRsrcCtx, "PolicyAddMismatchNameDom1", + "PolicyAddMismatchNameDom1.Policy1", auditRef, policy1); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "PolicyAddMismatchNameDom1", auditRef); + } + + @Test + public void testCreatePolicyInvalidName() { + + TopLevelDomain dom1 = createTopLevelDomainObject( + "PolicyAddInvalidNameDom1", "Test Domain1", "testOrg", + adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = new Policy(); + policy.setName("Policy1"); + + try { + zms.putPolicy(mockDomRsrcCtx, "PolicyAddInvalidNameDom1", "Policy1", auditRef, policy); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "PolicyAddInvalidNameDom1", auditRef); + } + + @Test + public void testCreatePolicyInvalidStruct() { + + TopLevelDomain dom1 = createTopLevelDomainObject( + "PolicyAddInvalidStructDom1", "Test Domain1", "testOrg", + adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = new Policy(); + + try { + zms.putPolicy(mockDomRsrcCtx, "PolicyAddInvalidStructDom1", "Policy1", auditRef, policy); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "PolicyAddInvalidStructDom1", auditRef); + } + + @Test + public void testDeletePolicy() { + + TopLevelDomain dom1 = createTopLevelDomainObject("PolicyDelDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject("PolicyDelDom1", "Policy1"); + zms.putPolicy(mockDomRsrcCtx, "PolicyDelDom1", "Policy1", auditRef, policy1); + + Policy policy2 = createPolicyObject("PolicyDelDom1", "Policy2"); + zms.putPolicy(mockDomRsrcCtx, "PolicyDelDom1", "Policy2", auditRef, policy2); + + Policy policyRes1 = zms.getPolicy(mockDomRsrcCtx, "PolicyDelDom1", "Policy1"); + assertNotNull(policyRes1); + + Policy policyRes2 = zms.getPolicy(mockDomRsrcCtx, "PolicyDelDom1", "Policy2"); + assertNotNull(policyRes2); + + zms.deletePolicy(mockDomRsrcCtx, "PolicyDelDom1", "Policy1", auditRef); + + // we need to get an exception here + try { + zms.getPolicy(mockDomRsrcCtx, "PolicyDelDom1", "Policy1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + policyRes2 = zms.getPolicy(mockDomRsrcCtx, "PolicyDelDom1", "Policy2"); + assertNotNull(policyRes2); + + zms.deletePolicy(mockDomRsrcCtx, "PolicyDelDom1", "Policy2", auditRef); + + // we need to get an exception here + try { + zms.getPolicy(mockDomRsrcCtx, "PolicyDelDom1", "Policy1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + // we need to get an exception here + try { + zms.getPolicy(mockDomRsrcCtx, "PolicyDelDom1", "Policy2"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "PolicyDelDom1", auditRef); + } + + @Test + public void testDeletePolicyThrowException() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_delpolhrowexc"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String caller = "deletepolicy"; + String domainName = "WrongDomainName"; + String policyName = "WrongPolicyName"; + try { + zmsImpl.deletePolicy(mockDomRsrcCtx, domainName, policyName, auditRef); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + FileConnection.deleteDirectory(new File(storeDir)); + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(deletepolicy: Unknown domain: wrongdomainname))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + } + + @Test + public void testDeleteAdminPolicy() { + + TopLevelDomain dom1 = createTopLevelDomainObject("PolicyAdminDelDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + try { + zms.deletePolicy(mockDomRsrcCtx, "PolicyAdminDelDom1", "admin", auditRef); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "PolicyAdminDelDom1", auditRef); + } + + @Test + public void testDeletePolicyMissingAuditRef() { + // create a new policy without an auditref + String domain = "testDeletePolicyMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, null, null, adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + try { + zms.deletePolicy(mockDomRsrcCtx, domain, "Policy1", null); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testCreateServiceIdentity() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceAddDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServiceAddDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceAddDom1", "Service1", auditRef, service); + + ServiceIdentity serviceRes2 = zms.getServiceIdentity(mockDomRsrcCtx, "ServiceAddDom1", + "Service1"); + assertNotNull(serviceRes2); + assertEquals(serviceRes2.getName(), "ServiceAddDom1.Service1".toLowerCase()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceAddDom1", auditRef); + } + + @Test + public void testCreateServiceIdentityNotSimpleName() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_createsvcidnosimplename"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceAddDom1NotSimpleName", + "Test Domain1", "testOrg", adminUser); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServiceAddDom1NotSimpleName", + "Service1.Test", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + try { + zmsImpl.putServiceIdentity(mockDomRsrcCtx, "ServiceAddDom1NotSimpleName", "Service1.Test", auditRef, service); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + String caller = "putserviceidentity"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + if (msg.indexOf("ERROR") == -1) { + continue; + } + assertTrue(msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + int index = msg.indexOf("WHAT-details=(ERROR=(Invalid SimpleName error: String pattern mismatch (expected \"[a-zA-Z0-9_][a-zA-Z0-9_-]*\") for type SimpleName in data)"); + assertTrue(index != -1); + foundError = true; + break; + } + assertTrue(foundError); + + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceAddDom1NotSimpleName", auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + + @Test + public void testCreateServiceIdentityMissingAuditRef() { + String domain = "testCreateServiceIdentityMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + ServiceIdentity service = createServiceObject( + domain, + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + try { + zms.putServiceIdentity(mockDomRsrcCtx, domain, "Service1", null, service); + fail("requesterror not thrown by putServiceIdentity."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testCreateServiceIdentityMismatchName() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceAddMismatchNameDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServiceAddMismatchNameDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + try { + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceAddMismatchNameDom1", + "ServiceAddMismatchNameDom1.Service1", auditRef, service); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceAddMismatchNameDom1", auditRef); + } + + @Test + public void testCreateServiceIdentityInvalidName() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceAddInvalidNameDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("Service1"); + + try { + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceAddInvalidNameDom1", "Service1", auditRef, service); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceAddInvalidNameDom1", auditRef); + } + + @Test + public void testCreateServiceIdentityInvalidCert() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceAddInvalidCertDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = new ServiceIdentity(); + service.setName(ZMSUtils.serviceResourceName("ServiceAddInvalidCertDom1", "Service1")); + List pubKeys = new ArrayList<>(); + pubKeys.add(new PublicKeyEntry().setId("0").setKey("LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTk")); + service.setPublicKeys(pubKeys); + + try { + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceAddInvalidCertDom1", "Service1", auditRef, service); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceAddInvalidCertDom1", auditRef); + } + + @Test + public void testCreateServiceIdentityInvalidStruct() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceAddInvalidStructDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = new ServiceIdentity(); + + try { + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceAddInvalidStructDom1", "Service1", auditRef, service); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceAddInvalidStructDom1", auditRef); + } + + @Test + public void testPutServiceIdentityThrowException() { + String domainName = "DomainName"; + String serviceName = "ServiceName"; + String wrongServiceName = "WrongServiceName"; + + // Tests the putServiceIdentity() condition: if (!serviceResourceName(domainName, serviceName).equals(detail.getName()))... + try { + ServiceIdentity detail = createServiceObject(domainName, + wrongServiceName, "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + // serviceName should not rendered to be the same as domainName:service.wrongServiceName + zms.putServiceIdentity(mockDomRsrcCtx, domainName, serviceName, auditRef, detail); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } + + // Tests the putServiceIdentity() condition: if (domain == null)... + try { + ServiceIdentity detail = createServiceObject(domainName, + serviceName, "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + // should fail b/c we never created a top level domain. + zms.putServiceIdentity(mockDomRsrcCtx, domainName, serviceName, auditRef, detail); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + } + + @Test + public void testGetServiceIdentity() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceGetDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServiceGetDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceGetDom1", "Service1", auditRef, service); + + ServiceIdentity serviceRes = zms.getServiceIdentity(mockDomRsrcCtx, "ServiceGetDom1", + "Service1"); + assertNotNull(serviceRes); + assertEquals(serviceRes.getName(), "ServiceGetDom1.Service1".toLowerCase()); + assertEquals(serviceRes.getExecutable(), "/usr/bin/java"); + assertEquals(serviceRes.getGroup(), "users"); + assertEquals(serviceRes.getProviderEndpoint().toString(), + "http://localhost"); + assertEquals(serviceRes.getUser(), "root"); + + List hosts = serviceRes.getHosts(); + assertNotNull(hosts); + assertEquals(hosts.size(), 1); + assertEquals(hosts.get(0), "host1"); + + // this should throw a not found exception + try { + zms.getServiceIdentity(mockDomRsrcCtx, "ServiceGetDom1", "Service2"); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 404); + } + + // this should throw a request error exception + try { + zms.getServiceIdentity(mockDomRsrcCtx, "ServiceGetDom1", "Service2.Service3"); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceGetDom1", auditRef); + } + + @Test + public void testGetServiceIdentityThrowException() { + String domainName = "ServiceGetDom1"; + String serviceName = "Service1"; + + // Tests the getServiceIdentity() condition : if (domain == null)... + try { + // Should fail because we never created this domain. + zms.getServiceIdentity(mockDomRsrcCtx, domainName, serviceName); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // Tests the getServiceIdentity() condition : if (collection == null)... + try { + // Should fail because we never added a service identity to this domain. + zms.getServiceIdentity(mockDomRsrcCtx, domainName, serviceName); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + // Tests the getServiceIdentity() condition : if (service == null)... + try { + String wrongServiceName = "Service2"; + + ServiceIdentity service = createServiceObject(domainName, + serviceName, "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + zms.putServiceIdentity(mockDomRsrcCtx, domainName, serviceName, auditRef, service); + + // Should fail because trying to find a non-existent service identity. + zms.getServiceIdentity(mockDomRsrcCtx, domainName, wrongServiceName); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testDeleteServiceIdentity() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceDelDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service1 = createServiceObject("ServiceDelDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceDelDom1", "Service1", auditRef, service1); + + ServiceIdentity service2 = createServiceObject("ServiceDelDom1", + "Service2", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceDelDom1", "Service2", auditRef, service2); + + ServiceIdentity serviceRes1 = zms.getServiceIdentity(mockDomRsrcCtx, "ServiceDelDom1", + "Service1"); + assertNotNull(serviceRes1); + + ServiceIdentity serviceRes2 = zms.getServiceIdentity(mockDomRsrcCtx, "ServiceDelDom1", + "Service2"); + assertNotNull(serviceRes2); + + zms.deleteServiceIdentity(mockDomRsrcCtx, "ServiceDelDom1", "Service1", auditRef); + + // this should throw a not found exception + try { + zms.getServiceIdentity(mockDomRsrcCtx, "ServiceDelDom1", "Service1"); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 404); + } + + serviceRes2 = zms.getServiceIdentity(mockDomRsrcCtx, "ServiceDelDom1", "Service2"); + assertNotNull(serviceRes2); + + zms.deleteServiceIdentity(mockDomRsrcCtx, "ServiceDelDom1", "Service2", auditRef); + + // this should throw a not found exception + try { + zms.getServiceIdentity(mockDomRsrcCtx, "ServiceDelDom1", "Service1"); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 404); + } + + // this should throw a not found exception + try { + zms.getServiceIdentity(mockDomRsrcCtx, "ServiceDelDom1", "Service2"); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 404); + } + + // this should throw an invalid exception + try { + zms.getServiceIdentity(mockDomRsrcCtx, "ServiceDelDom1", "Service2.Service3"); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceDelDom1", auditRef); + } + + @Test + public void testDeleteServiceIdentityMissingAuditRef() { + String domain = "testDeleteServiceIdentityMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + ServiceIdentity service = createServiceObject( + domain, + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + zms.putServiceIdentity(mockDomRsrcCtx, domain, "Service1", auditRef, service); + ServiceIdentity serviceRes = + zms.getServiceIdentity(mockDomRsrcCtx, domain, "Service1"); + assertNotNull(serviceRes); + try { + zms.deleteServiceIdentity(mockDomRsrcCtx, domain, "Service1", null); + fail("requesterror not thrown by deleteServiceIdentity."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testDeleteServiceIdentityThrowException() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_delsvcidthrowexc"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String caller = "deleteserviceidentity"; + String domainName = "WrongDomainName"; + String serviceName = "WrongServiceName"; + try { + zmsImpl.deleteServiceIdentity(mockDomRsrcCtx, domainName, serviceName, auditRef); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(deleteserviceidentity: Unknown domain: wrongdomainname))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + } + + @Test + public void testGetServiceIdentityList() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceListDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service1 = createServiceObject("ServiceListDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceListDom1", "Service1", auditRef, service1); + + ServiceIdentity service2 = createServiceObject("ServiceListDom1", + "Service2", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceListDom1", "Service2", auditRef, service2); + + ServiceIdentityList serviceList = zms.getServiceIdentityList( + mockDomRsrcCtx, "ServiceListDom1", null, null); + assertNotNull(serviceList); + assertEquals(serviceList.getNames().size(), 2); + + assertTrue(serviceList.getNames().contains("Service1".toLowerCase())); + assertTrue(serviceList.getNames().contains("Service2".toLowerCase())); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceListDom1", auditRef); + } + + @Test + public void testGetServiceIdentityListParams() { + + TopLevelDomain dom1 = createTopLevelDomainObject( + "ServiceListParamsDom1", "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service1 = createServiceObject("ServiceListParamsDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceListParamsDom1", "Service1", auditRef, service1); + + ServiceIdentity service2 = createServiceObject("ServiceListParamsDom1", + "Service2", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceListParamsDom1", "Service2", auditRef, service2); + + ServiceIdentityList serviceList = zms.getServiceIdentityList( + mockDomRsrcCtx, "ServiceListParamsDom1", 1, null); + assertNotNull(serviceList); + assertEquals(serviceList.getNames().size(), 1); + + serviceList = zms.getServiceIdentityList(mockDomRsrcCtx, "ServiceListParamsDom1", null, + "Service1"); + assertNotNull(serviceList); + assertEquals(serviceList.getNames().size(), 1); + + assertFalse(serviceList.getNames().contains("Service1".toLowerCase())); + assertTrue(serviceList.getNames().contains("Service2".toLowerCase())); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceListParamsDom1", auditRef); + } + + @Test + public void testGetServiceIdentityListThrowException() { + String domainName = "WrongDomainName"; + try { + zms.getServiceIdentityList(mockDomRsrcCtx, domainName, null, null); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + } + + @Test + public void testGetEntity() { + + TopLevelDomain dom1 = createTopLevelDomainObject("GetEntityDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Entity entity1 = createEntityObject("Entity1"); + zms.putEntity(mockDomRsrcCtx, "GetEntityDom1", "Entity1", auditRef, entity1); + + Entity entity2 = zms.getEntity(mockDomRsrcCtx, "GetEntityDom1", "Entity1"); + assertNotNull(entity2); + + assertEquals(entity2.getName(), "Entity1".toLowerCase()); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "GetEntityDom1", auditRef); + } + + @Test + public void testGetEntityThrowException() { + try { + zms.getEntity(mockDomRsrcCtx, "wrongDomainName", "wrongEntityName"); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + } + + @Test + public void testCreateEntity() { + + TopLevelDomain dom1 = createTopLevelDomainObject("CreateEntityDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Entity entity1 = createEntityObject("Entity1"); + zms.putEntity(mockDomRsrcCtx, "CreateEntityDom1", "Entity1", auditRef, entity1); + + Entity entity2 = zms.getEntity(mockDomRsrcCtx, "CreateEntityDom1", "Entity1"); + assertNotNull(entity2); + assertEquals(entity2.getName(), "Entity1".toLowerCase()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "CreateEntityDom1", auditRef); + } + + @Test + public void testListEntity() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ListEntityDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + EntityList entityList = zms.getEntityList(mockDomRsrcCtx, "ListEntityDom1"); + assertNotNull(entityList); + assertEquals(0, entityList.getNames().size()); + + Entity entity1 = createEntityObject("Entity1"); + zms.putEntity(mockDomRsrcCtx, "ListEntityDom1", "Entity1", auditRef, entity1); + + entityList = zms.getEntityList(mockDomRsrcCtx, "ListEntityDom1"); + assertNotNull(entityList); + assertEquals(1, entityList.getNames().size()); + assertTrue(entityList.getNames().contains("entity1")); + + Entity entity2 = createEntityObject("Entity2"); + zms.putEntity(mockDomRsrcCtx, "ListEntityDom1", "Entity2", auditRef, entity2); + + entityList = zms.getEntityList(mockDomRsrcCtx, "ListEntityDom1"); + assertNotNull(entityList); + assertEquals(2, entityList.getNames().size()); + assertTrue(entityList.getNames().contains("entity1")); + assertTrue(entityList.getNames().contains("entity2")); + + zms.deleteEntity(mockDomRsrcCtx, "ListEntityDom1", "entity1", auditRef); + + entityList = zms.getEntityList(mockDomRsrcCtx, "ListEntityDom1"); + assertNotNull(entityList); + assertEquals(1, entityList.getNames().size()); + assertTrue(entityList.getNames().contains("entity2")); + + zms.deleteEntity(mockDomRsrcCtx, "ListEntityDom1", "entity2", auditRef); + + entityList = zms.getEntityList(mockDomRsrcCtx, "ListEntityDom1"); + assertNotNull(entityList); + assertEquals(0, entityList.getNames().size()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ListEntityDom1", auditRef); + } + + @Test + public void testCreateEntityMissingAuditRef() { + String domain = "testCreateEntityMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Entity entity = createEntityObject("Entity1"); + try { + zms.putEntity(mockDomRsrcCtx, domain, "Entity1", null, entity); + fail("requesterror not thrown by putEntity."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testCreateEntityReservedNames() { + // create the weather domain + + TopLevelDomain dom = createTopLevelDomainObject("EntityReservedNames", + "Test entity", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Role role = createRoleObject("EntityReservedNames", "Role1", null, null, null); + zms.putRole(mockDomRsrcCtx, "EntityReservedNames", "Role1", auditRef, role); + + Policy policy = createPolicyObject("EntityReservedNames", "Policy1", + "Role1", "READ", "EntityReservedNames:*", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "EntityReservedNames", "Policy1", auditRef, policy); + + ServiceIdentity service = createServiceObject("EntityReservedNames", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "EntityReservedNames", "Service1", auditRef, service); + + Entity entity = createEntityObject("role"); + try { + zms.putEntity(mockDomRsrcCtx, "EntityReservedNames", "role", auditRef, entity); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + entity = createEntityObject("policy"); + try { + zms.putEntity(mockDomRsrcCtx, "EntityReservedNames", "policy", auditRef, entity); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + entity = createEntityObject("service"); + try { + zms.putEntity(mockDomRsrcCtx, "EntityReservedNames", "service", auditRef, entity); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + Role roleRes = zms.getRole(mockDomRsrcCtx, "EntityReservedNames", "Role1", false, false); + assertNotNull(roleRes); + + Policy policyRes = zms.getPolicy(mockDomRsrcCtx, "EntityReservedNames", "Policy1"); + assertNotNull(policyRes); + + ServiceIdentity serviceRes = zms.getServiceIdentity(mockDomRsrcCtx, "EntityReservedNames", "Service1"); + assertNotNull(serviceRes); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "EntityReservedNames", auditRef); + } + + @Test + public void testDeleteEntity() { + + TopLevelDomain dom1 = createTopLevelDomainObject("DelEntityDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Entity entity1 = createEntityObject("Entity1"); + zms.putEntity(mockDomRsrcCtx, "DelEntityDom1", "Entity1", auditRef, entity1); + + Entity entity2 = createEntityObject("Entity2"); + zms.putEntity(mockDomRsrcCtx, "DelEntityDom1", "Entity2", auditRef, entity2); + + Entity entityRes = zms.getEntity(mockDomRsrcCtx, "DelEntityDom1", "Entity1"); + assertNotNull(entityRes); + + entityRes = zms.getEntity(mockDomRsrcCtx, "DelEntityDom1", "Entity2"); + assertNotNull(entityRes); + + zms.deleteEntity(mockDomRsrcCtx, "DelEntityDom1", "Entity1", auditRef); + + try { + entityRes = zms.getEntity(mockDomRsrcCtx, "DelEntityDom1", "Entity1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + entityRes = zms.getEntity(mockDomRsrcCtx, "DelEntityDom1", "Entity2"); + assertNotNull(entityRes); + + zms.deleteEntity(mockDomRsrcCtx, "DelEntityDom1", "Entity2", auditRef); + + try { + entityRes = zms.getEntity(mockDomRsrcCtx, "DelEntityDom1", "Entity1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + try { + entityRes = zms.getEntity(mockDomRsrcCtx, "DelEntityDom1", "Entity2"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "DelEntityDom1", auditRef); + } + + @Test + public void testDeleteEntityMissingAuditRef() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_delentitymissauditref"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String domain = "testDeleteEntityMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Entity entity = createEntityObject("Entity1"); + zmsImpl.putEntity(mockDomRsrcCtx, domain, "Entity1", auditRef, entity); + + try { + zmsImpl.deleteEntity(mockDomRsrcCtx, domain, "Entity1", null); + fail("requesterror not thrown by deleteEntity."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + + String caller = "deleteentity"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(deleteentity: Audit reference required for domain: testdeleteentitymissingauditref))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + } finally { + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + } + + @Test + public void testGetUserToken() { + + // Use real Principal Authority to verify signatures + PrincipalAuthority principalAuthority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + principalAuthority.setKeyStore(zms); + + Authority userAuthority = new com.yahoo.athenz.common.server.debug.DebugUserAuthority(); + + String userId = "george"; + Principal principal = SimplePrincipal.create("user", userId, userId + ":password", + 0, userAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(userId); + ResourceContext rsrcCtx1 = createResourceContext(principal); + + zms.privateKeyId = "0"; + zms.privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + zms.publicKey = pubKey; + + UserToken token = zms.getUserToken(rsrcCtx1, userId, null); + assertNotNull(token); + assertTrue(token.getToken().startsWith("v=U1;d=user;n=" + userId + ";")); + assertTrue(token.getToken().contains(";h=localhost")); + assertTrue(token.getToken().contains(";i=10.11.12.13")); + assertTrue(token.getToken().contains(";k=0")); + // Verify signature + Principal principalToVerify = principalAuthority.authenticate(token.getToken(), "10.11.12.13", "GET", null); + assertNotNull(principalToVerify); + + zms.privateKeyId = "1"; + zms.privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKeyK1)); + zms.publicKey = pubKeyK1; + token = zms.getUserToken(rsrcCtx1, userId, null); + assertNotNull(token); + assertTrue(token.getToken().contains("k=1")); + // Verify signature + principalToVerify = principalAuthority.authenticate(token.getToken(), "10.11.12.13", "GET", null); + assertNotNull(principalToVerify); + + zms.privateKeyId = "2"; + zms.privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKeyK2)); + zms.publicKey = pubKeyK2; + + token = zms.getUserToken(rsrcCtx1, userId, null); + assertNotNull(token); + assertTrue(token.getToken().contains("k=2")); + // Verify signature + principalToVerify = principalAuthority.authenticate(token.getToken(), "10.11.12.13", "GET", null); + assertNotNull(principalToVerify); + } + + @Test + public void testGetUserTokenAuthorizedService() { + + Authority userAuthority = new com.yahoo.athenz.common.server.debug.DebugUserAuthority(); + + String userId = "george"; + Principal principal = SimplePrincipal.create("user", userId, userId + ":password", + 0, userAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(userId); + ResourceContext rsrcCtx1 = createResourceContext(principal); + + zms.privateKeyId = "0"; + zms.privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + zms.publicKey = pubKey; + + UserToken token = zms.getUserToken(rsrcCtx1, userId, "coretech.storage"); + assertNotNull(token); + assertTrue(token.getToken().contains(";b=coretech.storage;")); + + token = zms.getUserToken(rsrcCtx1, userId, "coretech.storage,sports.hockey"); + assertNotNull(token); + assertTrue(token.getToken().contains(";b=coretech.storage,sports.hockey;")); + } + + @Test + public void testGetUserTokenInvalidAuthorizedService() { + + Authority userAuthority = new com.yahoo.athenz.common.server.debug.DebugUserAuthority(); + + String userId = "george"; + Principal principal = SimplePrincipal.create("user", userId, userId + ":password", + 0, userAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(userId); + ResourceContext rsrcCtx1 = createResourceContext(principal); + + try { + zms.getUserToken(rsrcCtx1, userId, "coretech.storage,sports"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 401); + assertTrue(ex.getMessage().contains("getUserToken: Service sports is not authorized in ZMS")); + } + + try { + zms.getUserToken(rsrcCtx1, userId, "baseball"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 401); + assertTrue(ex.getMessage().contains("getUserToken: Service baseball is not authorized in ZMS")); + } + + try { + zms.getUserToken(rsrcCtx1, userId, "hat trick"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 401); + assertTrue(ex.getMessage().contains("getUserToken: Service hat trick is not authorized in ZMS")); + } + } + + @Test + public void testGetUserTokenExpiredIssueTime() { + + // Use real Principal Authority to verify signatures + PrincipalAuthority principalAuthority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + principalAuthority.setKeyStore(zms); + + // we're going to set the issue time 2 hours before the current time + + long issueTime = (System.currentTimeMillis() / 1000) - 7200; + + Authority userAuthority = new com.yahoo.athenz.common.server.debug.DebugUserAuthority(); + + String userId = "george"; + Principal principal = SimplePrincipal.create("user", userId, userId + ":password", + 0, userAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(userId); + ResourceContext rsrcCtx1 = createResourceContext(principal); + + zms.privateKeyId = "0"; + zms.privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + zms.publicKey = pubKey; + + UserToken token = zms.getUserToken(rsrcCtx1, userId, null); + assertNotNull(token); + // Verify signature + Principal principalToVerify = principalAuthority.authenticate(token.getToken(), "10.11.12.13", "GET", null); + assertNotNull(principalToVerify); + + // verify that the issue time for the user token is not our issue time + + PrincipalToken pToken = new PrincipalToken(token.getToken()); + assertNotEquals(pToken.getTimestamp(), issueTime); + + // verify that our expiry is close to 1 hour default value + + assertTrue(pToken.getExpiryTime() - (System.currentTimeMillis() / 1000) > 3500); + } + + @Test + public void testGetUserTokenMismatchName() { + int code = 401; + + Authority userAuthority = new com.yahoo.athenz.common.server.debug.DebugUserAuthority(); + + String userId = "user1"; + Principal principal = SimplePrincipal.create("user", userId, userId + ":password", + 0, userAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(userId); + ResourceContext rsrcCtx1 = createResourceContext(principal); + + try { + zms.getUserToken(rsrcCtx1, "user2", null); + fail("unauthorizederror not thrown."); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), code); + } + + try { + zms.getUserToken(rsrcCtx1, "_self", null); + fail("unauthorizederror not thrown."); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), code); + } + + try { + zms.getUserToken(rsrcCtx1, "self", null); + fail("unauthorizederror not thrown."); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), code); + } + } + + @Test + public void testGetUserTokenDefaultSelfName() { + + // Use real Principal Authority to verify signatures + PrincipalAuthority principalAuthority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + principalAuthority.setKeyStore(zms); + + Authority userAuthority = new com.yahoo.athenz.common.server.debug.DebugUserAuthority(); + + String userId = "user10"; + Principal principal = SimplePrincipal.create("user", userId, userId + ":password", + 0, userAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(userId); + ResourceContext rsrcCtx1 = createResourceContext(principal); + + zms.privateKeyId = "0"; + zms.privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + zms.publicKey = pubKey; + + UserToken token = zms.getUserToken(rsrcCtx1, "_self_", null); + assertNotNull(token); + assertTrue(token.getToken().startsWith("v=U1;d=user;n=" + userId + ";")); + assertTrue(token.getToken().contains(";h=localhost")); + assertTrue(token.getToken().contains(";i=10.11.12.13")); + assertTrue(token.getToken().contains(";k=0")); + // Verify signature + Principal principalToVerify = principalAuthority.authenticate(token.getToken(), "10.11.12.13", "GET", null); + assertNotNull(principalToVerify); + } + + @Test + public void testGetUserTokenBadAuthority() { + int code = 401; + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal = SimplePrincipal.create("user", "user1", "v=U1;d=user;n=user1;s=signature", + 0, principalAuthority); + ResourceContext rsrcCtx1 = createResourceContext(principal); + + try { + zms.getUserToken(rsrcCtx1, "user1", null); + fail("unauthorizederror not thrown."); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), code); + } + } + + @Test + public void testGetUserTokenNullAuthority() { + int code = 401; + + Principal principal = SimplePrincipal.create("user", "user1", "v=U1;d=user;n=user1;s=signature"); + ResourceContext rsrcCtx1 = createResourceContext(principal); + + try { + zms.getUserToken(rsrcCtx1, "user1", null); + fail("unauthorizederror not thrown."); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), code); + } + } + + @Test + public void testDeleteTenantRoles() { + + setupTenantDomainProviderService("DelTenantRolesDom1", "coretech", "storage", + "http://localhost:8090/tableprovider"); + + TenantRoles roles = zms.getTenantRoles(mockDomRsrcCtx, "coretech", "storage", + "DelTenantRolesDom1"); + assertNotNull(roles); + assertEquals(roles.getDomain(), "coretech"); + assertEquals(roles.getService(), "storage"); + assertEquals(roles.getTenant(), "DelTenantRolesDom1".toLowerCase()); + assertEquals(roles.getRoles().size(), 0); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + TenantRoles tenantRoles = new TenantRoles().setDomain("coretech") + .setService("storage").setTenant("DelTenantRolesDom1") + .setRoles(roleActions); + + zms.putTenantRoles(mockDomRsrcCtx, "coretech", "storage", "DelTenantRolesDom1", + auditRef, tenantRoles); + + RoleList roleList = zms.getRoleList(mockDomRsrcCtx, "coretech", null, null); + assertNotNull(roleList); + + boolean readerFound = false; + boolean writerFound = false; + for (String roleName : roleList.getNames()) { + if (roleName.contains("reader")) { + readerFound = true; + } else if (roleName.contains("writer")) { + writerFound = true; + } + } + + assertTrue(readerFound); + assertTrue(writerFound); + + PolicyList policyList = zms.getPolicyList(mockDomRsrcCtx, "coretech", null, null); + assertNotNull(policyList); + + readerFound = false; + writerFound = false; + for (String policy : policyList.getNames()) { + if (policy.contains("reader")) { + readerFound = true; + } else if (policy.contains("writer")) { + writerFound = true; + } + } + + assertTrue(readerFound); + assertTrue(writerFound); + + zms.deleteTenantRoles(mockDomRsrcCtx, "coretech", "storage", "DelTenantRolesDom1", auditRef); + + roleList = zms.getRoleList(mockDomRsrcCtx, "coretech", null, null); + assertNotNull(roleList); + + readerFound = false; + writerFound = false; + for (String roleName : roleList.getNames()) { + if (roleName.contains("reader")) { + readerFound = true; + } else if (roleName.contains("writer")) { + writerFound = true; + } + } + + assertFalse(readerFound); + assertFalse(writerFound); + + policyList = zms.getPolicyList(mockDomRsrcCtx, "coretech", null, null); + assertNotNull(policyList); + + readerFound = false; + writerFound = false; + for (String policy : policyList.getNames()) { + if (policy.contains("reader")) { + readerFound = true; + } else if (policy.contains("writer")) { + writerFound = true; + } + } + + assertFalse(readerFound); + assertFalse(writerFound); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "DelTenantRolesDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + } + + @Test + public void testDeleteTenantRolesWithResourceGroup() { + + String domain = "testDeleteTenantRoles"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + String serviceName = "storage"; + String tenantDomain = "tenantTestDeleteTenantRoles"; + String resourceGroup = "Group1"; + + TenantResourceGroupRoles tenantRoles = new TenantResourceGroupRoles().setDomain(domain) + .setService(serviceName).setTenant(tenantDomain) + .setRoles(roleActions).setResourceGroup(resourceGroup); + zms.putTenantResourceGroupRoles(mockDomRsrcCtx, domain, serviceName, tenantDomain, resourceGroup, + auditRef, tenantRoles); + + TenantResourceGroupRoles tRoles = zms.getTenantResourceGroupRoles(mockDomRsrcCtx, domain, serviceName, + tenantDomain, resourceGroup); + assertNotNull(tRoles); + assertEquals(domain.toLowerCase(), tRoles.getDomain()); + assertEquals(serviceName.toLowerCase(), tRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), tRoles.getTenant()); + assertEquals(resourceGroup.toLowerCase(), tRoles.getResourceGroup()); + assertEquals(TABLE_PROVIDER_ROLE_ACTIONS.size(), tRoles.getRoles().size()); + + zms.deleteTenantResourceGroupRoles(mockDomRsrcCtx, domain, serviceName, tenantDomain, resourceGroup, auditRef); + + tRoles = zms.getTenantResourceGroupRoles(mockDomRsrcCtx, domain, serviceName, tenantDomain, resourceGroup); + assertNotNull(tRoles); + assertEquals(domain.toLowerCase(), tRoles.getDomain()); + assertEquals(serviceName.toLowerCase(), tRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), tRoles.getTenant()); + assertEquals(resourceGroup.toLowerCase(), tRoles.getResourceGroup()); + assertEquals(0, tRoles.getRoles().size()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "testDeleteTenantRoles", auditRef); + } + + @Test + public void testDeleteTenantRolesMissingAuditRef() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_deltenantrolesmissauditref"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String domain = "testDeleteTenantRolesMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + String serviceName = "storage"; + String tenantDomain = "tenantTestDeleteTenantRolesMissingAuditRef"; + TenantRoles tenantRoles = new TenantRoles().setDomain(domain) + .setService(serviceName).setTenant(tenantDomain) + .setRoles(roleActions); + zmsImpl.putTenantRoles(mockDomRsrcCtx, domain, serviceName, tenantDomain, auditRef, tenantRoles); + + TenantRoles tRoles = zmsImpl.getTenantRoles(mockDomRsrcCtx, domain, serviceName, tenantDomain); + assertNotNull(tRoles); + assertEquals(tRoles.getDomain(), domain.toLowerCase()); + assertEquals(tRoles.getService(), serviceName.toLowerCase()); + assertEquals(tRoles.getTenant(), tenantDomain.toLowerCase()); + assertEquals(tRoles.getRoles().size(), TABLE_PROVIDER_ROLE_ACTIONS.size()); + + try { + zmsImpl.deleteTenantRoles(mockDomRsrcCtx, domain, serviceName, tenantDomain, null); + fail("requesterror not thrown by deleteTenantRoles."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + + String caller = "deletetenantroles"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(deletetenantroles: Audit reference required for domain: testdeletetenantrolesmissingauditref);:caller specified provider-service=(storage))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + + } finally { + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + } + + @Test + public void testValidatedAdminUsersThrowException() { + try { + zms.validatedAdminUsers(null); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } + } + + @Test + public void testPutDefaultAdmins() { + + TopLevelDomain sportsDomain = createTopLevelDomainObject("sports", + "Test domain for sports", "testOrg", adminUser); + try { + zms.deleteTopLevelDomain(mockDomRsrcCtx, sportsDomain.getName(), auditRef); + } catch (ResourceException ex) { + } + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, sportsDomain); + + List adminList = new ArrayList(); + DefaultAdmins admins = new DefaultAdmins(); + + // negative test, pass an empty list + admins.setAdmins(adminList); + zms.putDefaultAdmins(mockDomRsrcCtx, "sports", auditRef, admins); + + Role role = zms.getRole(mockDomRsrcCtx, "sports", "admin", false, false); + assertNotNull(role); + assertEquals(role.getName(), "sports:role.admin"); + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 1); + assertEquals(members.get(0), adminUser); + + // positive test + adminList.add("user.sports_admin"); + adminList.add("sports.fantasy"); + adminList.add("user.joeschmoe"); + adminList.add("user.johndoe"); + + admins.setAdmins(adminList); + zms.putDefaultAdmins(mockDomRsrcCtx, "sports", auditRef, admins); + + role = zms.getRole(mockDomRsrcCtx, "sports", "admin", false, false); + assertNotNull(role); + assertEquals(role.getName(), "sports:role.admin"); + members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 5); + + // add user.testadminuser to the list for verification since it should be + // there when the domain was added + adminList.add(adminUser); + for (String admin : adminList) { + boolean found = false; + for (String memberFromRole : members) { + if (memberFromRole.equalsIgnoreCase(admin)) { + found = true; + break; + } + } + assertTrue(found); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, sportsDomain.getName(), auditRef); + } + + @Test + public void testPutDefaultAdminsMissingAuditRef() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_putdefaminsmissauditref"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String domain = "testPutDefaultAdminsMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + List adminList = new ArrayList(); + adminList.add("user.sports_admin"); + adminList.add("sports.fantasy"); + DefaultAdmins admins = new DefaultAdmins(); + admins.setAdmins(adminList); + try { + zmsImpl.putDefaultAdmins(mockDomRsrcCtx, domain, null, admins); + fail("requesterror not thrown by putDefaultAdmins."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + + String caller = "putdefaultadmins"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(putdefaultadmins: Audit reference required for domain: testputdefaultadminsmissingauditref);:caller specified default-admins=(\"user.sports_admin\",\"sports.fantasy\"))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + } finally { + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + } + + @Test + public void testPutDefaultAdmins_NoAdminRole() { + + TopLevelDomain sportsDomain = createTopLevelDomainObject("sports", + "Test domain for sports", "testOrg", adminUser); + + try { + zms.deleteTopLevelDomain(mockDomRsrcCtx, sportsDomain.getName(), auditRef); + } catch (ResourceException ex) { + } + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, sportsDomain); + + // since we can't delete the admin role anymore + // we're going to access the store object directly to + // accomplish that for our unit test + + zms.dbService.executeDeleteRole(mockDomRsrcCtx, sportsDomain.getName(), "admin", + auditRef, "unittest"); + + List adminList = new ArrayList(); + DefaultAdmins admins = new DefaultAdmins(); + adminList.add("user.sports_admin"); + adminList.add("sports.fantasy"); + adminList.add("user.joeschmoe"); + adminList.add("user.johndoe"); + adminList.add(adminUser); + + admins.setAdmins(adminList); + zms.putDefaultAdmins(mockDomRsrcCtx, "sports", auditRef, admins); + + Role role = zms.getRole(mockDomRsrcCtx, "sports", "admin", false, false); + assertNotNull(role); + assertEquals(role.getName(), "sports:role.admin"); + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 5); + + // add user.testadminuser to the list for verification since it should be + // there when the domain was added + adminList.add(adminUser); + for (String admin : adminList) { + boolean found = false; + for (String memberFromRole : members) { + if (memberFromRole.equalsIgnoreCase(admin)) { + found = true; + break; + } + } + assertTrue(found); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, sportsDomain.getName(), auditRef); + } + + @Test + public void testPutDefaultAdmins_NoAdminPolicy() { + + TopLevelDomain sportsDomain = createTopLevelDomainObject("sports", + "Test domain for sports", "testOrg", adminUser); + try { + zms.deleteTopLevelDomain(mockDomRsrcCtx, sportsDomain.getName(), auditRef); + } catch (ResourceException ex) { + } + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, sportsDomain); + + // since we can't delete the admin policy anymore + // we're going to access the store object directly to + // accomplish that for our unit test + + zms.dbService.executeDeletePolicy(mockDomRsrcCtx, sportsDomain.getName(), "admin", + auditRef, "unittest"); + + List adminList = new ArrayList(); + DefaultAdmins admins = new DefaultAdmins(); + adminList.add("user.sports_admin"); + adminList.add("sports.fantasy"); + adminList.add("user.joeschmoe"); + adminList.add("user.johndoe"); + + admins.setAdmins(adminList); + zms.putDefaultAdmins(mockDomRsrcCtx, "sports", auditRef, admins); + + // Validate that admin policy has been added back + Policy policy = zms.getPolicy(mockDomRsrcCtx, "sports", "admin"); + assertNotNull(policy); + assertEquals(policy.getName(), "sports:policy.admin"); + List assertions = policy.getAssertions(); + boolean foundAssertion = false; + for (Assertion assertion : assertions) { + if ("sports:*".equals(assertion.getResource()) + && "*".equals(assertion.getAction()) + && "sports:role.admin".equals(assertion.getRole())) { + foundAssertion = true; + } + } + assertTrue(foundAssertion); + + Role role = zms.getRole(mockDomRsrcCtx, "sports", "admin", false, false); + assertNotNull(role); + assertEquals(role.getName(), "sports:role.admin"); + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 5); + + for (String admin : adminList) { + boolean found = false; + for (String memberFromRole : members) { + if (memberFromRole.equalsIgnoreCase(admin)) { + found = true; + break; + } + } + assertTrue(found); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, sportsDomain.getName(), auditRef); + } + + @Test + public void testPutDefaultAdmins_AdminPolicyWithDeny() { + + TopLevelDomain sportsDomain = createTopLevelDomainObject("sports", + "Test domain for sports", "testOrg", adminUser); + try { + zms.deleteTopLevelDomain(mockDomRsrcCtx, sportsDomain.getName(), auditRef); + } catch (ResourceException ex) { + } + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, sportsDomain); + + // Add policy which will DENY admin role + Policy policy = new Policy(); + policy.setName(ZMSUtils.policyResourceName("sports", "denyAdmin")); + Assertion assertion = new Assertion(); + assertion.setResource("sports:*"); + assertion.setAction("*"); + assertion.setRole("sports:role.admin"); + assertion.setEffect(AssertionEffect.DENY); + List assertions = new ArrayList(); + assertions.add(assertion); + policy.setAssertions(assertions); + zms.putPolicy(mockDomRsrcCtx, "sports", "denyAdmin", auditRef, policy); + + List adminList = new ArrayList(); + DefaultAdmins admins = new DefaultAdmins(); + adminList.add("user.sports_admin"); + adminList.add("sports.fantasy"); + adminList.add("user.joeschmoe"); + adminList.add("user.johndoe"); + + admins.setAdmins(adminList); + zms.putDefaultAdmins(mockDomRsrcCtx, "sports", auditRef, admins); + + // denyAdmin policy should be deleted by putDefaultAdmins validation + try { + policy = zms.getPolicy(mockDomRsrcCtx, "sports", "denyAdmin"); + assertNotNull(policy); // should not be found + } catch (ResourceException ex) { + // policy should not be found + if (ex.getCode() != 404) { + throw ex; + } + } + + Role role = zms.getRole(mockDomRsrcCtx, "sports", "admin", false, false); + assertNotNull(role); + assertEquals(role.getName(), "sports:role.admin"); + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 5); + + for (String admin : adminList) { + boolean found = false; + for (String memberFromRole : members) { + if (memberFromRole.equalsIgnoreCase(admin)) { + found = true; + break; + } + } + assertTrue(found); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, sportsDomain.getName(), auditRef); + } + + @Test + public void testPutDefaultAdmins_DenyIndirectRole() { + + TopLevelDomain sportsDomain = createTopLevelDomainObject("sports", + "Test domain for sports", "testOrg", adminUser); + try { + zms.deleteTopLevelDomain(mockDomRsrcCtx, sportsDomain.getName(), auditRef); + } catch (ResourceException ex) { + } + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, sportsDomain); + + // Add role indirectRole + Role role = new Role(); + role.setName("sports:role.indirectRole"); + List members = new ArrayList(); + members.add("user.johnadams"); + members.add("user.sports_admin"); + members.add("sports.fantasy"); + members.add("user.joeschmoe"); + members.add("user.johndoe"); + role.setMembers(members); + role.setTrust(null); + zms.putRole(mockDomRsrcCtx, "sports", "indirectRole", auditRef, role); + + // Add policy which will DENY indirectRole role + Policy policy = new Policy(); + policy.setName(ZMSUtils.policyResourceName("sports", "denyIndirectRole")); + Assertion assertion = new Assertion(); + assertion.setResource("sports:*"); + assertion.setAction("*"); + assertion.setRole("sports:role.indirectRole"); + assertion.setEffect(AssertionEffect.DENY); + List assertions = new ArrayList(); + assertions.add(assertion); + policy.setAssertions(assertions); + zms.putPolicy(mockDomRsrcCtx, "sports", "denyIndirectRole", auditRef, policy); + + List adminList = new ArrayList(); + DefaultAdmins admins = new DefaultAdmins(); + adminList.add("user.sports_admin"); + adminList.add("sports.fantasy"); + adminList.add("user.joeschmoe"); + adminList.add("user.johndoe"); + + admins.setAdmins(adminList); + zms.putDefaultAdmins(mockDomRsrcCtx, "sports", auditRef, admins); + + role = zms.getRole(mockDomRsrcCtx, "sports", "indirectRole", false, false); + assertNotNull(role); + assertEquals(role.getName(), "sports:role.indirectRole".toLowerCase()); + members = role.getMembers(); + assertEquals(members.size(), 1); + + for (String admin : adminList) { + boolean found = false; + for (String memberFromRole : members) { + if (memberFromRole.equalsIgnoreCase(admin)) { + found = true; + break; + } + } + assertFalse(found); + } + + role = zms.getRole(mockDomRsrcCtx, "sports", "admin", false, false); + assertNotNull(role); + assertEquals(role.getName(), "sports:role.admin"); + members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 5); + + for (String admin : adminList) { + boolean found = false; + for (String memberFromRole : members) { + if (memberFromRole.equalsIgnoreCase(admin)) { + found = true; + break; + } + } + assertTrue(found); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, sportsDomain.getName(), auditRef); + } + + @Test + public void testManageTenantRoles() { + + setupTenantDomainProviderService("AddTenantRolesDom1", "coretech", "storage", + "http://localhost:8090/tableprovider"); + + TenantRoles roles = zms.getTenantRoles(mockDomRsrcCtx, "coretech", "storage", + "AddTenantRolesDom1"); + assertNotNull(roles); + assertEquals(roles.getDomain(), "coretech"); + assertEquals(roles.getService(), "storage"); + assertEquals(roles.getTenant(), "AddTenantRolesDom1".toLowerCase()); + assertEquals(roles.getRoles().size(), 0); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + TenantRoles tenantRoles = new TenantRoles().setDomain("coretech") + .setService("storage").setTenant("AddTenantRolesDom1") + .setRoles(roleActions); + + zms.putTenantRoles(mockDomRsrcCtx, "coretech", "storage", "AddTenantRolesDom1", + auditRef, tenantRoles); + + roles = zms.getTenantRoles(mockDomRsrcCtx, "coretech", "storage", "AddTenantRolesDom1"); + assertNotNull(roles); + assertEquals(roles.getDomain(), "coretech"); + assertEquals(roles.getService(), "storage"); + assertEquals(roles.getTenant(), "AddTenantRolesDom1".toLowerCase()); + assertEquals(roles.getRoles().size(), 3); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "AddTenantRolesDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + } + + @Test + public void testGetSignedDomains() { + + // create multiple top level domains + TopLevelDomain dom1 = createTopLevelDomainObject("SignedDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // set the meta attributes for domain + + DomainMeta meta = createDomainMetaObject("Tenant Domain1", null, true, false, "12345", 0); + zms.putDomainMeta(mockDomRsrcCtx, "signeddom1", auditRef, meta); + + TopLevelDomain dom2 = createTopLevelDomainObject("SignedDom2", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + meta = createDomainMetaObject("Tenant Domain2", null, true, false, "12346", 0); + zms.putDomainMeta(mockDomRsrcCtx, "signeddom2", auditRef, meta); + + DomainList domList = zms.getDomainList(mockDomRsrcCtx, null, null, null, null, + null, null, null, null, null); + List domNames = domList.getNames(); + int numDoms = domNames.size(); + + zms.privateKeyId = "0"; + zms.privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + zms.publicKey = pubKey; + + GetSignedDomainsResult result = new GetSignedDomainsResult(mockDomRsrcCtx); + SignedDomains sdoms = null; + try { + zms.getSignedDomains(mockDomRsrcCtx, null, null, null, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + Object obj = getWebAppExcEntity(wexc); + sdoms = (SignedDomains) obj; + } + assertNotNull(sdoms); + List list = sdoms.getDomains(); + assertNotNull(list); + assertEquals(list.size(), numDoms); + + boolean dom1Found = false; + boolean dom2Found = false; + for(SignedDomain sDomain : list) { + String signature = sDomain.getSignature(); + String keyId = sDomain.getKeyId(); + String publicKey = zms.getPublicKey("sys.auth", "zms", keyId); + DomainData domainData = sDomain.getDomain(); + if (domainData.getName().equals("signeddom1")) { + assertEquals("12345", domainData.getAccount()); + dom1Found = true; + } else if (domainData.getName().equals("signeddom2")) { + assertEquals("12346", domainData.getAccount()); + dom2Found = true; + } + assertTrue(Crypto.verify(SignUtils.asCanonicalString(sDomain.getDomain()), Crypto.loadPublicKey(publicKey), signature)); + } + assertTrue(dom1Found); + assertTrue(dom2Found); + + zms.privateKeyId = "1"; + zms.privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKeyK1)); + zms.publicKey = pubKeyK1; + + result = new GetSignedDomainsResult(mockDomRsrcCtx); + sdoms = null; + try { + zms.getSignedDomains(mockDomRsrcCtx, null, null, null, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + Object obj = getWebAppExcEntity(wexc); + sdoms = (SignedDomains) obj; + } + assertNotNull(sdoms); + list = null; + list = sdoms.getDomains(); + assertNotNull(list); + assertEquals(list.size(), numDoms); + + for(SignedDomain sDomain : list) { + String signature = sDomain.getSignature(); + String keyId = sDomain.getKeyId(); + String publicKey = zms.getPublicKey("sys.auth", "zms", keyId); + assertTrue(Crypto.verify(SignUtils.asCanonicalString(sDomain.getDomain()), Crypto.loadPublicKey(publicKey), signature)); + + // we now need to verify the policy struct signature as well + + SignedPolicies signedPolicies = sDomain.getDomain().getPolicies(); + signature = signedPolicies.getSignature(); + keyId = signedPolicies.getKeyId(); + assertTrue(Crypto.verify(SignUtils.asCanonicalString(signedPolicies.getContents()), Crypto.loadPublicKey(publicKey), signature)); + } + + zms.privateKeyId = "2"; + zms.privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKeyK2)); + zms.publicKey = pubKeyK2; + + result = new GetSignedDomainsResult(mockDomRsrcCtx); + sdoms = null; + try { + zms.getSignedDomains(mockDomRsrcCtx, null, null, null, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + Object obj = getWebAppExcEntity(wexc); + sdoms = (SignedDomains) obj; + } + assertNotNull(sdoms); + + list = null; + list = sdoms.getDomains(); + assertNotNull(list); + assertEquals(list.size(), numDoms); + + for(SignedDomain sDomain : list) { + String signature = sDomain.getSignature(); + String keyId = sDomain.getKeyId(); + String publicKey = zms.getPublicKey("sys.auth", "zms", keyId); + assertTrue(Crypto.verify(SignUtils.asCanonicalString(sDomain.getDomain()), Crypto.loadPublicKey(publicKey), signature)); + } + + // test metaonly=true + // + result = new GetSignedDomainsResult(mockDomRsrcCtx); + sdoms = null; + try { + zms.getSignedDomains(mockDomRsrcCtx, null, "tRuE", null, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + Object obj = getWebAppExcEntity(wexc); + sdoms = (SignedDomains) obj; + } + assertNotNull(sdoms); + + list = null; + list = sdoms.getDomains(); + assertNotNull(list); + assertEquals(list.size(), numDoms); + + for (SignedDomain sDomain : list) { + String signature = sDomain.getSignature(); + assertTrue(signature == null || signature.isEmpty()); + String keyId = sDomain.getKeyId(); + assertTrue(keyId == null || keyId.isEmpty()); + DomainData ddata = sDomain.getDomain(); + assertTrue(ddata != null); + assertFalse(ddata.getName().isEmpty()); + assertTrue(ddata.getModified() != null); + assertTrue(ddata.getPolicies() == null); + assertTrue(ddata.getRoles() == null); + assertTrue(ddata.getServices() == null); + } + + // test metaonly=garbage + // + result = new GetSignedDomainsResult(mockDomRsrcCtx); + sdoms = null; + try { + zms.getSignedDomains(mockDomRsrcCtx, null, "garbage", null, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + Object obj = getWebAppExcEntity(wexc); + sdoms = (SignedDomains) obj; + } + assertNotNull(sdoms); + + list = null; + list = sdoms.getDomains(); + assertNotNull(list); + assertEquals(list.size(), numDoms); + + for (SignedDomain sDomain : list) { + String signature = sDomain.getSignature(); + String keyId = sDomain.getKeyId(); + String publicKey = zms.getPublicKey("sys.auth", "zms", keyId); + assertTrue(Crypto.verify(SignUtils.asCanonicalString(sDomain.getDomain()), Crypto.loadPublicKey(publicKey), signature)); + DomainData ddata = sDomain.getDomain(); + assertTrue(ddata.getPolicies() != null); + assertTrue(ddata.getRoles() != null && ddata.getRoles().size() > 0); + assertTrue(ddata.getServices() != null); + } + + // test metaonly=false + // + result = new GetSignedDomainsResult(mockDomRsrcCtx); + sdoms = null; + try { + zms.getSignedDomains(mockDomRsrcCtx, null, "fAlSe", null, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + Object obj = getWebAppExcEntity(wexc); + sdoms = (SignedDomains) obj; + } + assertNotNull(sdoms); + + list = null; + list = sdoms.getDomains(); + assertNotNull(list); + assertEquals(list.size(), numDoms); + + for (SignedDomain sDomain : list) { + String signature = sDomain.getSignature(); + String keyId = sDomain.getKeyId(); + String publicKey = zms.getPublicKey("sys.auth", "zms", keyId); + assertTrue(Crypto.verify(SignUtils.asCanonicalString(sDomain.getDomain()), Crypto.loadPublicKey(publicKey), signature)); + DomainData ddata = sDomain.getDomain(); + assertTrue(ddata.getPolicies() != null); + assertTrue(ddata.getRoles() != null && ddata.getRoles().size() > 0); + assertTrue(ddata.getServices() != null); + } + + // test bad tag format + // + String eTag = new String("I am not good"); + String eTag2 = null; + result = new GetSignedDomainsResult(mockDomRsrcCtx); + try { + zms.getSignedDomains(mockDomRsrcCtx, null, null, eTag, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + Object obj = getWebAppExcEntity(wexc); + sdoms = (SignedDomains) obj; + Object val = getWebAppExcMapValue(wexc, "ETag"); + eTag2 = val.toString(); + } + + assertNotNull(eTag2); + assertNotEquals(eTag, eTag2); + list = null; + list = sdoms.getDomains(); + assertNotNull(list); + assertEquals(list.size(), numDoms); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + + Policy policy1 = createPolicyObject("SignedDom1", "Policy1"); + zms.putPolicy(mockDomRsrcCtx, "SignedDom1", "Policy1", auditRef, policy1); + + result = new GetSignedDomainsResult(mockDomRsrcCtx); + sdoms = null; + eTag = null; + try { + zms.getSignedDomains(mockDomRsrcCtx, null, null, eTag2, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + Object obj = getWebAppExcEntity(wexc); + sdoms = (SignedDomains) obj; + Object val = getWebAppExcMapValue(wexc, "ETag"); + eTag = val.toString(); + } + + assertNotNull(eTag); + assertNotEquals(eTag, eTag2); + list = sdoms.getDomains(); + assertNotNull(list); + assertEquals(1, list.size()); + + result = new GetSignedDomainsResult(mockDomRsrcCtx); + sdoms = null; + eTag2 = null; + try { + zms.getSignedDomains(mockDomRsrcCtx, null, null, eTag, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + assertTrue(wexc.getResponse().getStatus() == 304); + Object val = getWebAppExcMapValue(wexc, "ETag"); + eTag2 = val.toString(); + } + assertNull(sdoms); + + assertNotNull(eTag2); + assertEquals(eTag, eTag2); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "SignedDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "SignedDom2", auditRef); + } + + @Test + public void testGetSignedDomainsFiltered() { + + // create multiple top level domains + TopLevelDomain dom1 = createTopLevelDomainObject("signeddom1filtered", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("signeddom2filtered", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + zms.privateKeyId = "0"; + zms.privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + zms.publicKey = pubKey; + + GetSignedDomainsResult result = new GetSignedDomainsResult(mockDomRsrcCtx); + SignedDomains sdoms = null; + try { + zms.getSignedDomains(mockDomRsrcCtx, "signeddom1filtered", null, null, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + Object obj = getWebAppExcEntity(wexc); + sdoms = (SignedDomains) obj; + } + assertNotNull(sdoms); + List list = sdoms.getDomains(); + assertNotNull(list); + assertEquals(1, list.size()); + + SignedDomain sDomain = list.get(0); + String signature = sDomain.getSignature(); + String keyId = sDomain.getKeyId(); + String publicKey = zms.getPublicKey("sys.auth", "zms", keyId); + assertTrue(Crypto.verify(SignUtils.asCanonicalString(sDomain.getDomain()), Crypto.loadPublicKey(publicKey), signature)); + assertEquals("signeddom1filtered", sDomain.getDomain().getName()); + + // use domain=signeddom1filtered and metaonly=true + // + result = new GetSignedDomainsResult(mockDomRsrcCtx); + sdoms = null; + try { + zms.getSignedDomains(mockDomRsrcCtx, "signeddom1filtered", "true", null, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + Object obj = getWebAppExcEntity(wexc); + sdoms = (SignedDomains) obj; + } + assertNotNull(sdoms); + list = sdoms.getDomains(); + assertNotNull(list); + assertEquals(1, list.size()); + + sDomain = list.get(0); + signature = sDomain.getSignature(); + assertTrue(signature == null || signature.isEmpty()); + keyId = sDomain.getKeyId(); + assertTrue(keyId == null || keyId.isEmpty()); + DomainData ddata = sDomain.getDomain(); + assertEquals("signeddom1filtered", ddata.getName()); + assertTrue(ddata.getModified() != null); + assertTrue(ddata.getPolicies() == null); + assertTrue(ddata.getRoles() == null); + assertTrue(ddata.getServices() == null); + + // no changes, we should still get the same data back + // we're going to pass the domain name with caps and + // make sure we still get back our domain + + result = new GetSignedDomainsResult(mockDomRsrcCtx); + sdoms = null; + try { + zms.getSignedDomains(mockDomRsrcCtx, "SignedDom1Filtered", null, null, result); + fail("webappexc not thrown by getSignedDomains"); + } catch (javax.ws.rs.WebApplicationException wexc) { + Object obj = getWebAppExcEntity(wexc); + sdoms = (SignedDomains) obj; + } + assertNotNull(sdoms); + list = null; + list = sdoms.getDomains(); + assertNotNull(list); + assertEquals(1, list.size()); + + sDomain = list.get(0); + signature = sDomain.getSignature(); + keyId = sDomain.getKeyId(); + publicKey = zms.getPublicKey("sys.auth", "zms", keyId); + assertTrue(Crypto.verify(SignUtils.asCanonicalString(sDomain.getDomain()), Crypto.loadPublicKey(publicKey), signature)); + assertEquals("signeddom1filtered", sDomain.getDomain().getName()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "signeddom1filtered", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "signeddom2filtered", auditRef); + } + + @Test + public void testGetAccess() { + + TopLevelDomain dom1 = createTopLevelDomainObject("AccessDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("AccessDom1", "Role1", null, "user.user1", + "user.user3"); + zms.putRole(mockDomRsrcCtx, "AccessDom1", "Role1", auditRef, role1); + + Role role2 = createRoleObject("AccessDom1", "Role2", null, "user.user2", + "user.user3"); + zms.putRole(mockDomRsrcCtx, "AccessDom1", "Role2", auditRef, role2); + + Policy policy1 = createPolicyObject("AccessDom1", "Policy1", "Role1", + "UPDATE", "AccessDom1:resource1", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "AccessDom1", "Policy1", auditRef, policy1); + + Policy policy2 = createPolicyObject("AccessDom1", "Policy2", "Role2", + "CREATE", "AccessDom1:resource2", AssertionEffect.DENY); + zms.putPolicy(mockDomRsrcCtx, "AccessDom1", "Policy2", auditRef, policy2); + + Policy policy3 = createPolicyObject("AccessDom1", "Policy3", "Role2", + "*", "AccessDom1:resource3", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "AccessDom1", "Policy3", auditRef, policy3); + + Policy policy4 = createPolicyObject("AccessDom1", "Policy4", "Role2", + "DELETE", "accessdom1:*", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "AccessDom1", "Policy4", auditRef, policy4); + + Policy policy5 = createPolicyObject("AccessDom1", "Policy5", "Role1", + "READ", "accessdom1:*", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "AccessDom1", "Policy5", auditRef, policy5); + + Policy policy6 = createPolicyObject("AccessDom1", "Policy6", "Role1", + "READ", "AccessDom1:resource6", AssertionEffect.DENY); + zms.putPolicy(mockDomRsrcCtx, "AccessDom1", "Policy6", auditRef, policy6); + + // user1 and user3 have access to UPDATE/resource1 + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal1 = principalAuthority.authenticate("v=U1;d=user;n=user1;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtx1 = createResourceContext(principal1); + Principal principal2 = principalAuthority.authenticate("v=U1;d=user;n=user2;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtx2 = createResourceContext(principal2); + Principal principal3 = principalAuthority.authenticate("v=U1;d=user;n=user3;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtx3 = createResourceContext(principal3); + + Access access = zms.getAccess(rsrcCtx1, "UPDATE", "AccessDom1:resource1", + "AccessDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "UPDATE", "AccessDom1:resource1", + "AccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "UPDATE", "AccessDom1:resource1", + "AccessDom1", null); + assertTrue(access.getGranted()); + + // same set as before with no trust domain field + + access = zms.getAccess(rsrcCtx1, "UPDATE", "AccessDom1:resource1", + null, null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "UPDATE", "AccessDom1:resource1", + null, null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "UPDATE", "AccessDom1:resource1", + null, null); + assertTrue(access.getGranted()); + + // all three have no access to CREATE action on resource1 + + access = zms.getAccess(rsrcCtx1, "CREATE", "AccessDom1:resource1", + "AccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "CREATE", "AccessDom1:resource1", + "AccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "CREATE", "AccessDom1:resource1", + "AccessDom1", null); + assertFalse(access.getGranted()); + + // all three have no access to invalid domain name on resource 1 + + access = zms.getAccess(rsrcCtx1, "CREATE", "AccessDom1:resource1", + "AccessDom2", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "CREATE", "AccessDom1:resource1", + "AccessDom2", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "CREATE", "AccessDom1:resource1", + "AccessDom2", null); + assertFalse(access.getGranted()); + + // same as before with no trust domain field + + access = zms.getAccess(rsrcCtx1, "CREATE", "AccessDom1:resource1", + null, null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "CREATE", "AccessDom1:resource1", + null, null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "CREATE", "AccessDom1:resource1", + null, null); + assertFalse(access.getGranted()); + + // all three should have deny access to resource 2 + + access = zms.getAccess(rsrcCtx1, "CREATE", "AccessDom1:resource2", + "AccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "CREATE", "AccessDom1:resource2", + "AccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "CREATE", "AccessDom1:resource2", + "AccessDom1", null); + assertFalse(access.getGranted()); + + // user2 and user3 have access to CREATE(*)/resource 3 + + access = zms.getAccess(rsrcCtx1, "CREATE", "AccessDom1:resource3", + "AccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "CREATE", "AccessDom1:resource3", + "AccessDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "CREATE", "AccessDom1:resource3", + "AccessDom1", null); + assertTrue(access.getGranted()); + + // user2 and user3 have access to UPDATE(*)/resource 3 + + access = zms.getAccess(rsrcCtx1, "UPDATE", "AccessDom1:resource3", + "AccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "UPDATE", "AccessDom1:resource3", + "AccessDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "UPDATE", "AccessDom1:resource3", + "AccessDom1", null); + assertTrue(access.getGranted()); + + // user2 and user3 have access to DELETE/resource 4 (*) + + access = zms.getAccess(rsrcCtx1, "DELETE", "AccessDom1:resource4", + "AccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "DELETE", "AccessDom1:resource4", + "AccessDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "DELETE", "AccessDom1:resource4", + "AccessDom1", null); + assertTrue(access.getGranted()); + + // user1 should be able to read resource 5(*) but not resource 6 + // (explicit DENY) + + access = zms.getAccess(rsrcCtx1, "READ", "AccessDom1:resource5", + "AccessDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "READ", "AccessDom1:resource6", + "AccessDom1", null); + assertFalse(access.getGranted()); + + // we should get an exception since access is not allowed to be called + // with user cookie - this api is only for functions that require a + // service or user tokens + + try { + zms.access("READ", "AccessDom1:resource5", principal1, "AccessDom1"); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "AccessDom1", auditRef); + } + + @Test + public void testGetAccessCrossUser() { + + TopLevelDomain dom1 = createTopLevelDomainObject("CrossAllowDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("CrossAllowDom1", "Role1", null, + "user.user1", "user.user3"); + zms.putRole(mockDomRsrcCtx, "CrossAllowDom1", "Role1", auditRef, role1); + + Role role2 = createRoleObject("CrossAllowDom1", "Role2", null, + "user.user2", "user.user3"); + zms.putRole(mockDomRsrcCtx, "CrossAllowDom1", "Role2", auditRef, role2); + + Role role3 = createRoleObject("CrossAllowDom1", "Role3", null, + "user.user1", null); + zms.putRole(mockDomRsrcCtx, "CrossAllowDom1", "Role3", auditRef, role3); + + Policy policy1 = createPolicyObject("CrossAllowDom1", "Policy1", + "Role1", "UPDATE", "CrossAllowDom1:resource1", + AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "CrossAllowDom1", "Policy1", auditRef, policy1); + + Policy policy2 = createPolicyObject("CrossAllowDom1", "Policy2", + "Role2", "CREATE", "CrossAllowDom1:resource2", + AssertionEffect.DENY); + zms.putPolicy(mockDomRsrcCtx, "CrossAllowDom1", "Policy2", auditRef, policy2); + + Policy policy3 = createPolicyObject("CrossAllowDom1", "Policy3", + "Role2", "*", "CrossAllowDom1:resource3", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "CrossAllowDom1", "Policy3", auditRef, policy3); + + Policy policy4 = createPolicyObject("CrossAllowDom1", "Policy4", + "Role2", "DELETE", "CrossAllowDom1:*", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "CrossAllowDom1", "Policy4", auditRef, policy4); + + // verify we have allow access for access resource + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal1 = principalAuthority.authenticate("v=U1;d=user;n=user1;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtx1 = createResourceContext(principal1); + Principal principal2 = principalAuthority.authenticate("v=U1;d=user;n=user2;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtx2 = createResourceContext(principal2); + Principal principal3 = principalAuthority.authenticate("v=U1;d=user;n=user3;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtx3 = createResourceContext(principal3); + + // user1 and user3 have access to UPDATE/resource1 + + Access access = zms.getAccess(rsrcCtx1, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user1"); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user.user1"); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user2"); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user.user2"); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user3"); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user.user3"); + assertTrue(access.getGranted()); + + // all three have no access to CREATE action on resource1 + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user1"); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user.user1"); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user2"); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user.user2"); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user3"); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user.user3"); + assertFalse(access.getGranted()); + + // all three have no access to invalid domain name on resource 1 + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom2", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom2", "user1"); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom2", "user.user1"); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "CREATE", "CrossAllowDom1:resource1", + "CrossAllowDom2", null); + assertFalse(access.getGranted()); + + // user2 and user3 have access to CREATE(*)/resource 3 + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource3", + "CrossAllowDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource3", + "CrossAllowDom1", "user1"); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource3", + "CrossAllowDom1", "user.user1"); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "CREATE", "CrossAllowDom1:resource3", + "CrossAllowDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource3", + "CrossAllowDom1", "user2"); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource3", + "CrossAllowDom1", "user.user2"); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "CREATE", "CrossAllowDom1:resource3", + "CrossAllowDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource3", + "CrossAllowDom1", "user3"); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx1, "CREATE", "CrossAllowDom1:resource3", + "CrossAllowDom1", "user.user3"); + assertTrue(access.getGranted()); + + // user2 and user3 are allowed to check each other's access + + access = zms.getAccess(rsrcCtx2, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user1"); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx2, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user.user1"); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user1"); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtx3, "UPDATE", "CrossAllowDom1:resource1", + "CrossAllowDom1", "user.user1"); + assertTrue(access.getGranted()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "CrossAllowDom1", auditRef); + } + + @Test + public void testGetAccessHomeDomainEnabled() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN, "true"); + ZMSImpl zmsTest = zmsInit(); + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + + Principal pJane = principalAuthority.authenticate("v=U1;d=user;n=jane;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtxJane = createResourceContext(pJane); + + Access access = zmsTest.getAccess(rsrcCtxJane, "READ", "user.jane:Resource1", null, null); + assertTrue(access.getGranted()); + + access = zmsTest.getAccess(rsrcCtxJane, "WRITE", "user.jane:Resource1", null, null); + assertTrue(access.getGranted()); + + access = zmsTest.getAccess(rsrcCtxJane, "UPDATE", "user.jane:Resource1", null, null); + assertTrue(access.getGranted()); + + // user id does not match yrn domain - all should be failure + + Principal pJohn = principalAuthority.authenticate("v=U1;d=user;n=john;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtxJohn = createResourceContext(pJohn); + + try { + zmsTest.getAccess(rsrcCtxJohn, "READ", "user.jane:Resource1", null, null); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zmsTest.getAccess(rsrcCtxJohn, "WRITE", "user.jane:Resource1", null, null); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zmsTest.getAccess(rsrcCtxJohn, "UPDATE", "user.jane:Resource1", null, null); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN); + } + + @Test + public void testGetAccessHomeDomainDisabled() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN, "false"); + ZMSImpl zmsTest = zmsInit(); + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + + Principal pJane = principalAuthority.authenticate("v=U1;d=user;n=jane;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtxJane = createResourceContext(pJane); + + try { + zmsTest.getAccess(rsrcCtxJane, "READ", "user.jane:Resource1", null, null); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zmsTest.getAccess(rsrcCtxJane, "WRITE", "user.jane:Resource1", null, null); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zmsTest.getAccess(rsrcCtxJane, "UPDATE", "user.jane:Resource1", null, null); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN); + } + + @Test + public void testRetrieveAccessDomainValid() { + + TopLevelDomain dom1 = createTopLevelDomainObject("AccessDomain", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal pJane = principalAuthority.authenticate("v=U1;d=user;n=jane;s=signature", "10.11.12.13", "GET", null); + + AthenzDomain athenzDomain = zms.retrieveAccessDomain("accessdomain", pJane); + assertNotNull(athenzDomain); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "AccessDomain", auditRef); + } + + @Test + public void testRetrieveAccessDomainVirtualValid() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN, "true"); + ZMSImpl zmsTest = zmsInit(); + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal = SimplePrincipal.create("user", "user1", "v=U1;d=user;n=user1;s=signature", + 0, principalAuthority); + + AthenzDomain athenzDomain = zmsTest.retrieveAccessDomain("user.user1", principal); + assertNotNull(athenzDomain); + assertEquals(athenzDomain.getName(), "user.user1"); + + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN); + } + + @Test + public void testRetrieveAccessDomainVirtualDomainDisabled() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN, "false"); + ZMSImpl zmsTest = zmsInit(); + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal = SimplePrincipal.create("user", "user1", "v=U1;d=user;n=user1;s=signature", + 0, principalAuthority); + + AthenzDomain athenzDomain = zmsTest.retrieveAccessDomain("user.user1", principal); + assertNull(athenzDomain); + + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN); + } + + @Test + public void testRetrieveAccessDomainPrincialNullDomain() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN, "true"); + ZMSImpl zmsTest = zmsInit(); + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal = SimplePrincipal.create(null, "user1", "v=U1;d=user;n=user1;s=signature", + 0, principalAuthority); + + AthenzDomain athenzDomain = zmsTest.retrieveAccessDomain("user.user1", principal); + assertNull(athenzDomain); + + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN); + + } + + @Test + public void testRetrieveAccessDomainMismatch() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN, "true"); + ZMSImpl zmsTest = zmsInit(); + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal = SimplePrincipal.create("user", "user2", "v=U1;d=user;n=user2;s=signature", + 0, principalAuthority); + + AthenzDomain athenzDomain = zmsTest.retrieveAccessDomain("user.user1", principal); + assertNull(athenzDomain); + + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN); + } + + @Test + public void testGetAccessCrossDomain() { + + setupTenantDomainProviderService("CrossDomainAccessDom1", "coretech", "storage", + "http://localhost:8090/provider"); + + Tenancy tenant = createTenantObject("CrossDomainAccessDom1", "coretech.storage"); + ProviderMockClient.setReturnTenantRoles(true); + zms.putTenancy(mockDomRsrcCtx, "CrossDomainAccessDom1", "coretech.storage", auditRef, tenant); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction((String) f.value())); + } + TenantRoles tenantRoles = new TenantRoles().setDomain("coretech") + .setService("storage").setTenant("CrossDomainAccessDom1") + .setRoles(roleActions); + + zms.putTenantRoles(mockDomRsrcCtx, "coretech", "storage", "CrossDomainAccessDom1", + auditRef, tenantRoles); + + Tenancy tenant1 = zms.getTenancy(mockDomRsrcCtx, "CrossDomainAccessDom1", "coretech.storage"); + assertNotNull(tenant1); + + // reset roles in the CrossDomainAccessDom1 domain with unique values + + Role role = createRoleObject("CrossDomainAccessDom1", "reader", null, "user.joe", + "user.jane"); + zms.putRole(mockDomRsrcCtx, "CrossDomainAccessDom1", "reader", auditRef, role); + + role = createRoleObject("CrossDomainAccessDom1", "writer", null, "user.john", + "user.jane"); + zms.putRole(mockDomRsrcCtx, "CrossDomainAccessDom1", "writer", auditRef, role); + + Policy policy = createPolicyObject("CrossDomainAccessDom1", "tenancy.coretech.storage.writer", + "writer", "ASSUME_ROLE", "coretech:role.storage.tenant.CrossDomainAccessDom1.writer", + AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "CrossDomainAccessDom1", "tenancy.coretech.storage.writer", auditRef, policy); + + policy = createPolicyObject("CrossDomainAccessDom1", "tenancy.coretech.storage.reader", + "reader", "ASSUME_ROLE", "coretech:role.storage.tenant.CrossDomainAccessDom1.reader", + AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "CrossDomainAccessDom1", "tenancy.coretech.storage.reader", auditRef, policy); + + // verify the ASSUME_ROLE check - with trust domain specified it should work and + // without trust domain it will not work since the resource is pointing to the + // provider's domain and not to the tenant's domain + + Access access = zms.getAccess(mockDomRsrcCtx, "ASSUME_ROLE", "coretech:role.storage.tenant.CrossDomainAccessDom1.reader", + null, "user.jane"); + assertFalse(access.getGranted()); + + access = zms.getAccess(mockDomRsrcCtx, "ASSUME_ROLE", "coretech:role.storage.tenant.CrossDomainAccessDom1.reader", + "CrossDomainAccessDom1", "user.jane"); + assertTrue(access.getGranted()); + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + + Principal pJane = principalAuthority.authenticate("v=U1;d=user;n=jane;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtxJane = createResourceContext(pJane); + Principal pJohn = principalAuthority.authenticate("v=U1;d=user;n=john;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtxJohn = createResourceContext(pJohn); + Principal pJoe = principalAuthority.authenticate("v=U1;d=user;n=joe;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtxJoe = createResourceContext(pJoe); + + access = zms.getAccess(rsrcCtxJoe, "READ", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtxJane, "READ", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtxJohn, "READ", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtxJoe, "WRITE", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtxJane, "WRITE", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom1", null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtxJohn, "WRITE", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom1", null); + assertTrue(access.getGranted()); + + // unknown action should always fail + + access = zms.getAccess(rsrcCtxJoe, "UPDATE", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtxJane, "UPDATE", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom1", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtxJohn, "UPDATE", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom1", null); + assertFalse(access.getGranted()); + + // same set as above without trust domain field + + access = zms.getAccess(rsrcCtxJoe, "READ", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + null, null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtxJane, "READ", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + null, null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtxJohn, "READ", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + null, null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtxJoe, "WRITE", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + null, null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtxJane, "WRITE", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + null, null); + assertTrue(access.getGranted()); + + access = zms.getAccess(rsrcCtxJohn, "WRITE", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + null, null); + assertTrue(access.getGranted()); + + // failure with different domain name + + access = zms.getAccess(rsrcCtxJoe, "READ", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom2", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtxJane, "READ", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom2", null); + assertFalse(access.getGranted()); + + access = zms.getAccess(rsrcCtxJohn, "READ", "coretech:service.storage.tenant.CrossDomainAccessDom1.resource1", + "CrossDomainAccessDom2", null); + assertFalse(access.getGranted()); + + zms.deleteTenancy(mockDomRsrcCtx, "CrossDomainAccessDom1", "coretech.storage", auditRef); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "CrossDomainAccessDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + } + + @Test + public void testGetAccessCrossDomainWildCardResources() { + + // create the netops domain + + TopLevelDomain dom = createTopLevelDomainObject("netops", + "Test Netops", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + Role role = createRoleObject("netops", "users", null, null, null); + zms.putRole(mockDomRsrcCtx, "netops", "users", auditRef, role); + + role = createRoleObject("netops", "superusers", null, "user.siteops_user_1", + "user.siteops_user_2"); + zms.putRole(mockDomRsrcCtx, "netops", "superusers", auditRef, role); + + Policy policy = createPolicyObject("netops", "users", + "users", "NODE_USER", "netops:node.", + AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "netops", "users", auditRef, policy); + + policy = createPolicyObject("netops", "superusers", + "superusers", "NODE_SUDO", "netops:node.", + AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "netops", "superusers", auditRef, policy); + + policy = createPolicyObject("netops", "netops_superusers", + "netops:role.superusers", false, "ASSUME_ROLE", "*:role.netops_superusers", + AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "netops", "netops_superusers", auditRef, policy); + + // create the weather domain + + dom = createTopLevelDomainObject("weather", + "Test weather", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + role = createRoleObject("weather", "users", null, null, null); + zms.putRole(mockDomRsrcCtx, "weather", "users", auditRef, role); + + role = createRoleObject("weather", "superusers", null, "user.weather_admin_user", + null); + zms.putRole(mockDomRsrcCtx, "weather", "superusers", auditRef, role); + + role = createRoleObject("weather", "netops_superusers", "netops"); + zms.putRole(mockDomRsrcCtx, "weather", "netops_superusers", auditRef, role); + + policy = createPolicyObject("weather", "users", + "users", "NODE_USER", "weather:node.", + AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "weather", "users", auditRef, policy); + + policy = createPolicyObject("weather", "superusers", + "superusers", "NODE_SUDO", "weather:node.*", + AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "weather", "superusers", auditRef, policy); + + policy = createPolicyObject("weather", "netops_superusers", + "netops_superusers", "NODE_SUDO", "weather:node.*", + AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "weather", "netops_superusers", auditRef, policy); + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + + Principal pWeather = principalAuthority.authenticate("v=U1;d=user;n=weather_admin_user;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtxWeather = createResourceContext(pWeather); + + Access access = zms.getAccess(rsrcCtxWeather, "NODE_SUDO", "weather:node.x", null, null); + assertTrue(access.getGranted()); + + Principal pSiteOps = principalAuthority.authenticate("v=U1;d=user;n=siteops_user_1;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtxSiteOps = createResourceContext(pSiteOps); + + access = zms.getAccess(rsrcCtxSiteOps, "NODE_SUDO", "weather:node.x", null, null); + assertTrue(access.getGranted()); + + Principal pRandom = principalAuthority.authenticate("v=U1;d=user;n=random_user;s=signature", "10.11.12.13", "GET", null); + ResourceContext rsrcCtxRandom = createResourceContext(pRandom); + + access = zms.getAccess(rsrcCtxRandom, "NODE_SUDO", "weather:node.x", null, null); + assertFalse(access.getGranted()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "weather", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "netops", auditRef); + } + + @Test + public void testValidateEntity() { + int code = 400; + String en = new String("entityOne"); + Entity entity = new Entity(); + String nonmatchName = new String("entityTwo"); + + // tests the condition: if (!en.equals(entity.getName()))... + try { + entity.setName(nonmatchName); + + zms.validateEntity(en, entity); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), code); + } + + // tests the condition: if (entity.getValue() == null)... + try { + entity.setName(en); + + zms.validateEntity(en, entity); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), code); + } + } + + @Test + public void testCheckReservedEntityName() { + int code = 400; + String reserved = new String("meta"); + try { + zms.checkReservedEntityName(reserved); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), code); + } + } + + @Test + public void testPutEntity() { + int code = 404; + String name = new String("entityOne"); + try { + Entity entity = createEntityObject(name); + + // entityName will not match entity.name. + zms.putEntity(mockDomRsrcCtx, "wrongDomainName", name, auditRef, entity); + fail("notfounderror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), code); + } + } + + @Test + public void testGetPublicKeyZMS() { + + String publicKey = zms.getPublicKey("sys.auth", "zms", "0"); + assertNotNull(publicKey); + try { + assertTrue(pubKey.equals(Crypto.ybase64(publicKey.getBytes("UTF-8")))); + } catch (UnsupportedEncodingException e) { + fail(); + } + + publicKey = zms.getPublicKey("sys.auth", "zms", "1"); + assertNotNull(publicKey); + try { + assertTrue(pubKeyK1.equals(Crypto.ybase64(publicKey.getBytes("UTF-8")))); + } catch (UnsupportedEncodingException e) { + fail(); + } + + publicKey = zms.getPublicKey("sys.auth", "zms", "2"); + assertNotNull(publicKey); + try { + assertTrue(pubKeyK2.equals(Crypto.ybase64(publicKey.getBytes("UTF-8")))); + } catch (UnsupportedEncodingException e) { + fail(); + } + } + + @Test + public void testGetPublicKeyInvalidService() { + + String pubKey = zms.getPublicKey("sys.auth", "sys.auth", "0"); + assertNull(pubKey); + } + + @Test + public void testGetPublicKeyService() { + + TopLevelDomain dom1 = createTopLevelDomainObject("GetPublicKeyDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("GetPublicKeyDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "GetPublicKeyDom1", "Service1", auditRef, service); + + String publicKey = zms.getPublicKey("GetPublicKeyDom1", "Service1", "0"); + assertNull(publicKey); + + assertNull(zms.getPublicKey("GetPublicKeyDom1", null, "0")); + assertNull(zms.getPublicKey("GetPublicKeyDom1", "Service1", null)); + + publicKey = zms.getPublicKey("GetPublicKeyDom1", "Service1", "1"); + assertNotNull(publicKey); + assertTrue(publicKey.equals(Crypto.ybase64DecodeString(pubKeyK1))); + + publicKey = zms.getPublicKey("GetPublicKeyDom1", "Service1", "2"); + assertNotNull(publicKey); + assertTrue(publicKey.equals(Crypto.ybase64DecodeString(pubKeyK2))); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "GetPublicKeyDom1", auditRef); + } + + @Test + public void testPutTenancy() { + + setupTenantDomainProviderService("AddTenancyDom1", "coretech", "storage", + "http://localhost:8090/provider"); + + Tenancy tenant = createTenantObject("AddTenancyDom1", "coretech.storage"); + ProviderMockClient.setReturnTenantRoles(true); + zms.putTenancy(mockDomRsrcCtx, "AddTenancyDom1", "coretech.storage", auditRef, tenant); + + // make sure our roles have been created + + Role role = zms.getRole(mockDomRsrcCtx, "AddTenancyDom1", "coretech.storage.admin", false, false); + assertNotNull(role); + + role = zms.getRole(mockDomRsrcCtx, "AddTenancyDom1", "coretech.storage.reader", false, false); + assertNotNull(role); + + role = zms.getRole(mockDomRsrcCtx, "AddTenancyDom1", "coretech.storage.writer", false, false); + assertNotNull(role); + + // verify the policies have the correct roles + + Policy policy = zms.getPolicy(mockDomRsrcCtx, "AddTenancyDom1", "tenancy.coretech.storage.admin"); + assertNotNull(policy); + + // the admin is a special case where we are going to create the policy + // before we call the provider's tenant controller service so we'll + // end up with 4 assertions + + List assertList = policy.getAssertions(); + assertEquals(4, assertList.size()); + + boolean domainAdminRoleCheck = false; + boolean tenantAdminRoleCheck = false; + boolean resourceAdminRoleCheck = false; + boolean tenantUpdateCheck = false; + for (Assertion obj : assertList) { + assertEquals(AssertionEffect.ALLOW, obj.getEffect()); + if (obj.getRole().equals("addtenancydom1:role.admin")) { + assertEquals(obj.getAction(), "assume_role"); + domainAdminRoleCheck = true; + } else if (obj.getRole().equals("addtenancydom1:role.tenancy.coretech.storage.admin")) { + if (obj.getAction().equals("assume_role")) { + tenantAdminRoleCheck = true; + } else if (obj.getAction().equals("update")) { + tenantUpdateCheck = true; + } + } else if (obj.getRole().equals("addtenancydom1:role.coretech.storage.admin")) { + assertEquals(obj.getAction(), "assume_role"); + resourceAdminRoleCheck = true; + } + } + assertTrue(domainAdminRoleCheck); + assertTrue(tenantAdminRoleCheck); + assertTrue(resourceAdminRoleCheck); + assertTrue(tenantUpdateCheck); + + policy = zms.getPolicy(mockDomRsrcCtx, "AddTenancyDom1", "tenancy.coretech.storage.reader"); + assertNotNull(policy); + + assertList = policy.getAssertions(); + assertEquals(assertList.size(), 1); + assertEquals(assertList.get(0).getRole(), "addtenancydom1:role.coretech.storage.reader"); + + policy = zms.getPolicy(mockDomRsrcCtx, "AddTenancyDom1", "tenancy.coretech.storage.writer"); + assertNotNull(policy); + + assertList = policy.getAssertions(); + assertEquals(assertList.size(), 1); + assertEquals(assertList.get(0).getRole(), "addtenancydom1:role.coretech.storage.writer"); + + // now add the tenant roles for our domain since our mock provider client + // cannot connect to our zms instance + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + TenantRoles tenantRoles = new TenantRoles().setDomain("coretech") + .setService("storage").setTenant("AddTenancyDom1") + .setRoles(roleActions); + + zms.putTenantRoles(mockDomRsrcCtx, "coretech", "storage", "AddTenancyDom1", + auditRef, tenantRoles); + + Tenancy tenant1 = zms.getTenancy(mockDomRsrcCtx, "AddTenancyDom1", "coretech.storage"); + assertNotNull(tenant1); + + zms.deleteTenancy(mockDomRsrcCtx, "AddTenancyDom1", "coretech.storage", auditRef); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "AddTenancyDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + } + + @Test + public void testPutTenancyWithAuthorizedService() { + + String tenantDomain = "puttenancyauthorizedservice"; + String providerService = "storage"; + String providerDomain = "coretech"; + String provider = providerDomain + "." + providerService; + + setupTenantDomainProviderService(tenantDomain, providerDomain, providerService, null); + + // tenant is setup so let's setup up policy to authorize access to tenants + // without this role/policy we won't be authorized to add tenant roles + // to the provider domain even with authorized service details + + Role role = createRoleObject(providerDomain, "self_serve", null, + providerDomain + "." + providerService, null); + zms.putRole(mockDomRsrcCtx, providerDomain, "self_serve", auditRef, role); + + Policy policy = createPolicyObject(providerDomain, "self_serve", + "self_serve", "update", providerDomain + ":tenant.*", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, providerDomain, "self_serve", auditRef, policy); + + // we are going to create a principal object with authorized service + // set to coretech.storage + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + String userId = "user1"; + String unsignedCreds = "v=U1;d=user;u=" + userId; + Principal principal = SimplePrincipal.create("user", userId, unsignedCreds + ";s=signature", 0, principalAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(unsignedCreds); + ((SimplePrincipal) principal).setAuthorizedService(provider); + ResourceContext ctx = createResourceContext(principal); + + // after this call we should have admin roles set for both provider and tenant + + Tenancy tenant = createTenantObject(tenantDomain, provider); + zms.putTenancy(ctx, tenantDomain, provider, auditRef, tenant); + + // make sure our policy has been created + + policy = zms.getPolicy(mockDomRsrcCtx, tenantDomain, "tenancy." + provider + ".admin"); + assertNotNull(policy); + + String tenantRoleInProviderDomain = providerService + ".tenant." + tenantDomain + ".admin"; + + List assertList = policy.getAssertions(); + assertEquals(3, assertList.size()); + boolean domainAdminRoleCheck = false; + boolean tenantAdminRoleCheck = false; + boolean tenantUpdateCheck = false; + for (Assertion obj : assertList) { + assertEquals(AssertionEffect.ALLOW, obj.getEffect()); + if (obj.getRole().equals(tenantDomain + ":role.admin")) { + assertEquals("assume_role", obj.getAction()); + assertEquals("coretech:role.storage.tenant.puttenancyauthorizedservice.admin", obj.getResource()); + domainAdminRoleCheck = true; + } else if (obj.getRole().equals(tenantDomain + ":role.tenancy." + provider + ".admin")) { + if (obj.getAction().equals("assume_role")) { + assertEquals("coretech:role.storage.tenant.puttenancyauthorizedservice.admin", obj.getResource()); + tenantAdminRoleCheck = true; + } else if (obj.getAction().equals("update")) { + assertEquals(tenantDomain + ":tenancy." + provider, obj.getResource()); + tenantUpdateCheck = true; + } + } + } + assertTrue(domainAdminRoleCheck); + assertTrue(tenantAdminRoleCheck); + assertTrue(tenantUpdateCheck); + + // now let's verify the provider side by using the get tenant roles call + + TenantRoles tRoles = zms.getTenantRoles(mockDomRsrcCtx, providerDomain, providerService, tenantDomain); + assertNotNull(tRoles); + assertEquals(1, tRoles.getRoles().size()); + TenantRoleAction roleAction = tRoles.getRoles().get(0); + assertEquals("*", roleAction.getAction()); + assertEquals("admin", roleAction.getRole()); + + role = zms.getRole(mockDomRsrcCtx, providerDomain, tenantRoleInProviderDomain, false, false); + assertNotNull(role); + + // now let's call delete tenancy support with the same authorized service token + + zms.deleteTenancy(ctx, tenantDomain, provider, auditRef); + + // verify that all roles and policies have been deleted + + try { + zms.getPolicy(mockDomRsrcCtx, tenantDomain, "tenancy." + provider + ".admin"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + try { + zms.getRole(mockDomRsrcCtx, providerDomain, tenantRoleInProviderDomain, false, false); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + // get tenant roles now returns an empty set + + tRoles = zms.getTenantRoles(mockDomRsrcCtx, providerDomain, providerService, tenantDomain); + assertNotNull(tRoles); + assertEquals(0, tRoles.getRoles().size()); + + // clean up our domains + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + } + + @Test + public void testPutTenancyWithAuthorizedServiceMismatch() { + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_puttenancywithauthsvcmism"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String tenantDomain = "puttenancyauthorizedservicemismatch"; + String providerService = "storage"; + String providerDomain = "coretech-test"; + String provider = providerDomain + "." + providerService; + + setupTenantDomainProviderService(zmsImpl, tenantDomain, providerDomain, providerService, null); + + // tenant is setup so let's setup up policy to authorize access to tenants + // without this role/policy we won't be authorized to add tenant roles + // to the provider domain even with authorized service details + + Role role = createRoleObject(providerDomain, "self_serve", null, + providerDomain + "." + providerService, null); + zmsImpl.putRole(mockDomRsrcCtx, providerDomain, "self_serve", auditRef, role); + + Policy policy = createPolicyObject(providerDomain, "self_serve", + "self_serve", "update", providerDomain + ":tenant.*", AssertionEffect.ALLOW); + zmsImpl.putPolicy(mockDomRsrcCtx, providerDomain, "self_serve", auditRef, policy); + + // we are going to create a principal object with authorized service + // set to coretech.storage + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + String userId = "user1"; + String unsignedCreds = "v=U1;d=user;u=" + userId; + Principal principal = SimplePrincipal.create("user", userId, unsignedCreds + ";s=signature", 0, principalAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(unsignedCreds); + ((SimplePrincipal) principal).setAuthorizedService("coretech.storage"); // make provider mismatch + ResourceContext ctx = createResourceContext(principal); + + // this should fail since the authorized service name does not + // match to the provider and there is no endpoint specified for the provider + + Tenancy tenant = createTenantObject(tenantDomain, provider); + try { + zmsImpl.putTenancy(ctx, tenantDomain, provider, auditRef, tenant); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + String caller = "puttenancy"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg.indexOf("WHAT-details=(ERROR=(putTenancy: tenant domain(puttenancyauthorizedservicemismatch): Cannot put tenancy on provider service=coretech-test.storage -- not a provider service)") != -1); + foundError = true; + break; + } + assertTrue(foundError); + + // clean up our domains + + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + + @Test + public void testPutTenancyWithoutTenantRoles() { + + setupTenantDomainProviderService("AddTenancyDom1", "coretech", "storage", + "http://localhost:8090/provider"); + + Tenancy tenant = createTenantObject("AddTenancyDom1", "coretech.storage"); + ProviderMockClient.setReturnTenantRoles(false); + zms.putTenancy(mockDomRsrcCtx, "AddTenancyDom1", "coretech.storage", auditRef, tenant); + + // make sure our roles have not been created + + try { + zms.getRole(mockDomRsrcCtx, "AddTenancyDom1", "coretech.storage.admin", false, false); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zms.getRole(mockDomRsrcCtx, "AddTenancyDom1", "coretech.storage.reader", false, false); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zms.getRole(mockDomRsrcCtx, "AddTenancyDom1", "coretech.storage.writer", false, false); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + // verify the admin policy has been successfully created + + Policy policy = zms.getPolicy(mockDomRsrcCtx, "AddTenancyDom1", "tenancy.coretech.storage.admin"); + assertNotNull(policy); + + // we should not have other policies for actions + + try { + zms.getPolicy(mockDomRsrcCtx, "AddTenancyDom1", "tenancy.coretech.storage.reader"); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zms.getPolicy(mockDomRsrcCtx, "AddTenancyDom1", "tenancy.coretech.storage.writer"); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + zms.deleteTenancy(mockDomRsrcCtx, "AddTenancyDom1", "coretech.storage", auditRef); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "AddTenancyDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + } + + @Test + public void testPutTenancyResourceGroup() { + + String domain = "addtenancyresourcegroupdom1"; + + setupTenantDomainProviderService(domain, "coretech", "storage", + "http://localhost:8090/provider"); + + Tenancy tenant = createTenantObject(domain, "coretech.storage"); + ProviderMockClient.setReturnTenantRoles(false); + zms.putTenancy(mockDomRsrcCtx, domain, "coretech.storage", auditRef, tenant); + + // make sure our roles have not been created + + try { + zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.admin", false, false); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.reader", false, false); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.writer", false, false); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + // verify the admin policy has been successfully created + + Policy policy = zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.admin"); + assertNotNull(policy); + + // we should not have other policies for actions + + try { + zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.reader"); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.writer"); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + // now let's put a resource group + + TenancyResourceGroup detail = new TenancyResourceGroup(); + detail.setDomain(domain).setService("coretech.storage").setResourceGroup("hockey"); + zms.putTenancyResourceGroup(mockDomRsrcCtx, domain, "coretech.storage", "hockey", auditRef, detail); + + // now verify that roles were created successfully + + Role role = zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.res_group.hockey.reader", false, false); + assertNotNull(role); + + role = zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.res_group.hockey.writer", false, false); + assertNotNull(role); + + // verify the policies were created successfully + + policy = zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.res_group.hockey.reader"); + assertNotNull(policy); + + List assertList = policy.getAssertions(); + assertEquals(assertList.size(), 1); + assertEquals(assertList.get(0).getRole(), domain + ":role.coretech.storage.res_group.hockey.reader"); + + policy = zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.res_group.hockey.writer"); + assertNotNull(policy); + + assertList = policy.getAssertions(); + assertEquals(assertList.size(), 1); + assertEquals(assertList.get(0).getRole(), domain + ":role.coretech.storage.res_group.hockey.writer"); + + // now add the tenant roles for our domain since our mock provider client + // cannot connect to our zms instance + + List roleActions = new ArrayList(); + for (Struct.Field f : RESOURCE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + TenantResourceGroupRoles tenantRoles = new TenantResourceGroupRoles().setDomain("coretech") + .setService("storage").setTenant(domain) + .setRoles(roleActions).setResourceGroup("hockey"); + + zms.putTenantResourceGroupRoles(mockDomRsrcCtx, "coretech", "storage", domain, "hockey", + auditRef, tenantRoles); + + List tenantResourceGroups = new ArrayList<>(); + tenantResourceGroups.add("hockey"); + ProviderMockClient.setResourceGroups(tenantResourceGroups); + + // let's verify that our get Tenancy returns the resource group + + Tenancy tenancy = zms.getTenancy(mockDomRsrcCtx, domain, "coretech.storage"); + assertNotNull(tenancy); + assertEquals(1, tenancy.getResourceGroups().size()); + assertTrue(tenancy.getResourceGroups().contains("hockey")); + ProviderMockClient.setResourceGroups(null); + + // now let's add another resource group + + detail = new TenancyResourceGroup(); + detail.setDomain(domain).setService("coretech.storage").setResourceGroup("baseball"); + zms.putTenancyResourceGroup(mockDomRsrcCtx, domain, "coretech.storage", "baseball", auditRef, detail); + + // now verify that roles were created successfully + + role = zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.res_group.baseball.reader", false, false); + assertNotNull(role); + + role = zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.res_group.baseball.writer", false, false); + assertNotNull(role); + + // verify the policies were created successfully + + policy = zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.res_group.baseball.reader"); + assertNotNull(policy); + + assertList = policy.getAssertions(); + assertEquals(assertList.size(), 1); + assertEquals(assertList.get(0).getRole(), domain + ":role.coretech.storage.res_group.baseball.reader"); + + policy = zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.res_group.baseball.writer"); + assertNotNull(policy); + + assertList = policy.getAssertions(); + assertEquals(assertList.size(), 1); + assertEquals(assertList.get(0).getRole(), domain + ":role.coretech.storage.res_group.baseball.writer"); + + // now add the tenant roles for our domain since our mock provider client + // cannot connect to our zms instance + + tenantRoles = new TenantResourceGroupRoles().setDomain("coretech") + .setService("storage").setTenant(domain) + .setRoles(roleActions).setResourceGroup("baseball"); + + zms.putTenantResourceGroupRoles(mockDomRsrcCtx, "coretech", "storage", domain, "baseball", + auditRef, tenantRoles); + + tenantResourceGroups.add("baseball"); + ProviderMockClient.setResourceGroups(tenantResourceGroups); + + tenancy = zms.getTenancy(mockDomRsrcCtx, domain, "coretech.storage"); + assertNotNull(tenancy); + assertEquals(2, tenancy.getResourceGroups().size()); + assertTrue(tenancy.getResourceGroups().contains("hockey")); + assertTrue(tenancy.getResourceGroups().contains("baseball")); + ProviderMockClient.setResourceGroups(null); + + // now we're going to let the provider an extra resource + // that must be removed by zms since there are no policies + // defined for that resource group + + tenantResourceGroups.add("basketball"); + ProviderMockClient.setResourceGroups(tenantResourceGroups); + + tenancy = zms.getTenancy(mockDomRsrcCtx, domain, "coretech.storage"); + assertNotNull(tenancy); + assertEquals(2, tenancy.getResourceGroups().size()); + assertTrue(tenancy.getResourceGroups().contains("hockey")); + assertTrue(tenancy.getResourceGroups().contains("baseball")); + ProviderMockClient.setResourceGroups(null); + + zms.deleteTenancy(mockDomRsrcCtx, domain, "coretech.storage", auditRef); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + } + + @Test + public void testPutTenancyMissingAuditRef() { + String tenantDomain = "testPutTenancyMissingAuditRef"; + String providerDomain = "providerTestPutTenancyMissingAuditRef"; + String providerService = "storage"; + + // create tenant and provider domains + // + setupTenantDomainProviderService(tenantDomain, providerDomain, providerService, + "http://localhost:8090/provider"); + + // modify the tenant domain to require auditing + // + DomainMeta meta = createDomainMetaObject("Tenant Domain", null, false, true, null, 0); + zms.putDomainMeta(mockDomRsrcCtx, tenantDomain, auditRef, meta); + + Tenancy tenant = createTenantObject(tenantDomain, providerDomain + "." + providerService); + try { + ProviderMockClient.setReturnTenantRoles(true); + zms.putTenancy(mockDomRsrcCtx, tenantDomain, providerDomain + "." + providerService, null, tenant); + fail("requesterror not thrown by putTenancy."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + } + } + + @Test + public void testPutTenancyThrowException() { + String domainName = "AddTenancyDom2"; + String tenantDomain = "coretech2"; + String tenantService = "storage"; + String service = tenantDomain + "." + tenantService; + + try { + // use invalid provider-endpoint. + setupTenantDomainProviderService(domainName, tenantDomain, tenantService, ""); + + Tenancy tenant = createTenantObject(domainName, service); + + // should fail because do not have a valid provider-endpoint. + ProviderMockClient.setReturnTenantRoles(true); + zms.putTenancy(mockDomRsrcCtx, domainName, service, auditRef, tenant); + fail("requesterror not thrown"); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + } + + @Test + public void testDeleteTenancy() { + String tenantDomain = "testDeleteTenancy"; + String providerDomain = "providerTestDeleteTenancy"; + String providerService = "storage"; + String provServiceYRN = providerDomain + "." + providerService; + + // create tenant and provider domains + // + setupTenantDomainProviderService(tenantDomain, providerDomain, providerService, + "http://localhost:8090/provider"); + + // modify the tenant domain to require auditing + // + DomainMeta meta = + createDomainMetaObject("Tenant Domain", null, false, true, null, 0); + zms.putDomainMeta(mockDomRsrcCtx, tenantDomain, auditRef, meta); + + String testRoleName = providerDomain + ".testrole"; + Role role = createRoleObject(tenantDomain, testRoleName, null, "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, tenantDomain, testRoleName, auditRef, role); + + // setup tenancy + // + Tenancy tenant = createTenantObject(tenantDomain, provServiceYRN); + ProviderMockClient.setReturnTenantRoles(true); + zms.putTenancy(mockDomRsrcCtx, tenantDomain, provServiceYRN, auditRef, tenant); + + try { + zms.deleteTenancy(mockDomRsrcCtx, tenantDomain, provServiceYRN, auditRef); + + // verify we didn't delete a role by mistake + + assertNotNull(zms.getRole(mockDomRsrcCtx, tenantDomain, testRoleName, false, false)); + + // verify that all roles and policies have been deleted + + try { + zms.getRole(mockDomRsrcCtx, tenantDomain, provServiceYRN + ".admin", false, false); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + try { + zms.getRole(mockDomRsrcCtx, tenantDomain, provServiceYRN + ".reader", false, false); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + try { + zms.getRole(mockDomRsrcCtx, tenantDomain, provServiceYRN + ".writer", false, false); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + try { + zms.getPolicy(mockDomRsrcCtx, tenantDomain, "tenancy." + provServiceYRN + ".admin"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + try { + zms.getPolicy(mockDomRsrcCtx, tenantDomain, "tenancy." + provServiceYRN + ".reader"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + try { + zms.getPolicy(mockDomRsrcCtx, tenantDomain, "tenancy." + provServiceYRN + ".writer"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + } + } + + @Test + public void testDeleteTenancyResourceGroup() { + + String domain = "deletetenancyresourcegroupdom1"; + + setupTenantDomainProviderService(domain, "coretech", "storage", + "http://localhost:8090/provider"); + + Tenancy tenant = createTenantObject(domain, "coretech.storage"); + ProviderMockClient.setReturnTenantRoles(false); + zms.putTenancy(mockDomRsrcCtx, domain, "coretech.storage", auditRef, tenant); + + // verify the admin policy has been successfully created + + Policy policy = zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.admin"); + assertNotNull(policy); + + // now let's put a resource group + + TenancyResourceGroup detail = new TenancyResourceGroup(); + detail.setDomain(domain).setService("coretech.storage").setResourceGroup("hockey"); + zms.putTenancyResourceGroup(mockDomRsrcCtx, domain, "coretech.storage", "hockey", auditRef, detail); + + // now let's add another resource group + + detail = new TenancyResourceGroup(); + detail.setDomain(domain).setService("coretech.storage").setResourceGroup("baseball"); + zms.putTenancyResourceGroup(mockDomRsrcCtx, domain, "coretech.storage", "baseball", auditRef, detail); + + // now let's delete our initial hockey resource group + + zms.deleteTenancyResourceGroup(mockDomRsrcCtx, domain, "coretech.storage", "hockey", auditRef); + + // verify the admin policy is still present + + policy = zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.admin"); + assertNotNull(policy); + + // now verify that baseball roles are still around + + Role role = zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.res_group.baseball.reader", false, false); + assertNotNull(role); + + role = zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.res_group.baseball.writer", false, false); + assertNotNull(role); + + // verify the policies also exist for baseball group + + policy = zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.res_group.baseball.reader"); + assertNotNull(policy); + + List assertList = policy.getAssertions(); + assertEquals(assertList.size(), 1); + assertEquals(assertList.get(0).getRole(), domain + ":role.coretech.storage.res_group.baseball.reader"); + + policy = zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.res_group.baseball.writer"); + assertNotNull(policy); + + assertList = policy.getAssertions(); + assertEquals(assertList.size(), 1); + assertEquals(assertList.get(0).getRole(), domain + ":role.coretech.storage.res_group.baseball.writer"); + + // now verify that the hockey roles and policies were indeed removed + + try { + zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.res_group.hockey.reader", false, false); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.res_group.hockey.writer", false, false); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + // we should not have other policies for actions + + try { + zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.res_group.hockey.reader"); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.res_group.hockey.writer"); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + // now let's delete the baseball resource group as well + + zms.deleteTenancyResourceGroup(mockDomRsrcCtx, domain, "coretech.storage", "baseball", auditRef); + + // now verify that the admin policy is still around + + policy = zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.admin"); + assertNotNull(policy); + + // now verify that the baseball roles and policies were indeed removed + + try { + zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.res_group.baseball.reader", false, false); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zms.getRole(mockDomRsrcCtx, domain, "coretech.storage.res_group.baseball.writer", false, false); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + // we should not have other policies for actions + + try { + zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.res_group.baseball.reader"); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + try { + zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.res_group.baseball.writer"); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + // now let's just delete some invalid resource group which should not + // affect anything since we have no roles/policies to delete + + zms.deleteTenancyResourceGroup(mockDomRsrcCtx, domain, "coretech.storage", "basketball", auditRef); + + // now verify that the admin policy is still around + + policy = zms.getPolicy(mockDomRsrcCtx, domain, "tenancy.coretech.storage.admin"); + assertNotNull(policy); + + zms.deleteTenancy(mockDomRsrcCtx, domain, "coretech.storage", auditRef); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "coretech", auditRef); + } + + @Test + public void testDeleteTenancyMissingService() { + String tenantDomain = "testDeleteTenancy"; + String providerDomain = "providerTestDeleteTenancy"; + String providerService = "storage"; + String provServiceYRN = providerDomain + "." + providerService; + + // create tenant and provider domains + // + setupTenantDomainProviderService(tenantDomain, providerDomain, providerService, + "http://localhost:8090/provider"); + + // modify the tenant domain to require auditing + // + DomainMeta meta = + createDomainMetaObject("Tenant Domain", null, false, true, null, 0); + zms.putDomainMeta(mockDomRsrcCtx, tenantDomain, auditRef, meta); + + // setup tenancy + // + Tenancy tenant = createTenantObject(tenantDomain, provServiceYRN); + ProviderMockClient.setReturnTenantRoles(true); + zms.putTenancy(mockDomRsrcCtx, tenantDomain, provServiceYRN, auditRef, tenant); + + // delete the provider service + + zms.deleteServiceIdentity(mockDomRsrcCtx, providerDomain, providerService, auditRef); + + try { + zms.deleteTenancy(mockDomRsrcCtx, tenantDomain, provServiceYRN, auditRef); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("service does not exist")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + } + } + + @Test + public void testDeleteTenancyMissingEndpoint() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_deltenancymissendpoint"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String tenantDomain = "testDeleteTenancyMissEnd"; + String providerDomain = "providerTestDeleteTenancyMissEnd"; + String providerService = "storage"; + String provServiceYRN = providerDomain + "." + providerService; + + // create tenant and provider domains + // + setupTenantDomainProviderService(zmsImpl, tenantDomain, providerDomain, providerService, + "http://localhost:8090/provider"); + + // modify the tenant domain to require auditing + // + DomainMeta meta = + createDomainMetaObject("Tenant Domain", null, false, true, null, 0); + zmsImpl.putDomainMeta(mockDomRsrcCtx, tenantDomain, auditRef, meta); + + // setup tenancy + // + Tenancy tenant = createTenantObject(tenantDomain, provServiceYRN); + ProviderMockClient.setReturnTenantRoles(true); + zmsImpl.putTenancy(mockDomRsrcCtx, tenantDomain, provServiceYRN, auditRef, tenant); + + // delete the provider service endpoint + + ServiceIdentity service = createServiceObject( + providerDomain, providerService, null, + "/usr/bin/java", "root", "users", "localhost"); + + zmsImpl.putServiceIdentity(mockDomRsrcCtx, providerDomain, providerService, auditRef, service); + + try { + zmsImpl.deleteTenancy(mockDomRsrcCtx, tenantDomain, provServiceYRN, auditRef); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("service does not have endpoint configured")); + + String caller = "deletetenancy"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + if (msg.indexOf("(ERROR=(deleteTenancy") == -1) { + continue; + } + assertTrue(msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + String errMsg = "WHAT-details=(ERROR=(deleteTenancy: Tenant cleanup in(testdeletetenancymissend): completed successfully. However, there was an error when contacting the Provider Service: providertestdeletetenancymissend.storage:service does not have endpoint configured"; + int index = msg.indexOf(errMsg); + assertTrue(index != -1); + foundError = true; + break; + } + assertTrue(foundError); + + } finally { + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + } + + @Test + public void testDeleteTenancyMissingAuditRef() { + String tenantDomain = "testDeleteTenancyMissingAuditRef"; + String providerDomain = "providerTestDeleteTenancyMissingAuditRef"; + String providerService = "storage"; + + // create tenant and provider domains + // + setupTenantDomainProviderService(tenantDomain, providerDomain, providerService, + "http://localhost:8090/provider"); + + // modify the tenant domain to require auditing + // + DomainMeta meta = + createDomainMetaObject("Tenant Domain", null, false, true, null, 0); + zms.putDomainMeta(mockDomRsrcCtx, tenantDomain, auditRef, meta); + + // setup tenancy + // + Tenancy tenant = createTenantObject(tenantDomain, providerDomain + "." + providerService); + ProviderMockClient.setReturnTenantRoles(true); + zms.putTenancy(mockDomRsrcCtx, tenantDomain, providerDomain + "." + providerService, auditRef, tenant); + + try { + zms.deleteTenancy(mockDomRsrcCtx, tenantDomain, providerDomain + "." + providerService, null); + fail("requesterror not thrown by deleteTenancy."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + } + } + + @Test + public void testPutTenantRoles() { + + String domain = "testPutTenantRoles"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + String serviceName = "storage"; + String tenantDomain = "tenantTestPutTenantRoles"; + TenantRoles tenantRoles = new TenantRoles().setDomain(domain) + .setService(serviceName).setTenant(tenantDomain) + .setRoles(roleActions); + zms.putTenantRoles(mockDomRsrcCtx, domain, serviceName, tenantDomain, auditRef, tenantRoles); + + TenantRoles tRoles = zms.getTenantRoles(mockDomRsrcCtx, domain, serviceName, tenantDomain); + assertNotNull(tRoles); + assertEquals(tRoles.getDomain(), domain.toLowerCase()); + assertEquals(tRoles.getService(), serviceName.toLowerCase()); + assertEquals(tRoles.getTenant(), tenantDomain.toLowerCase()); + assertEquals(tRoles.getRoles().size(), TABLE_PROVIDER_ROLE_ACTIONS.size()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + + @Test + public void testPutTenantRolesMissingAuditRef() { + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_puttenantrolesmissauditref"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String domain = "testPutTenantRoles"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + String serviceName = "storage"; + String tenantDomain = "tenantTestPutTenantRoles"; + TenantRoles tenantRoles = new TenantRoles().setDomain(domain) + .setService(serviceName).setTenant(tenantDomain) + .setRoles(roleActions); + try { + zmsImpl.putTenantRoles(mockDomRsrcCtx, domain, serviceName, tenantDomain, null, tenantRoles); + fail("requesterror not thrown by putTenantRoles."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + + String caller = "puttenantroles"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(puttenantroles: Audit reference required for domain: testputtenantroles);:caller specified provider-service=(storage))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + } finally { + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + } + + @Test + public void testPutTenantRolesThrowException() { + String domainName = "AddTenancyDom3"; + String tenantDomain = "coretech3"; + String tenantService = "storage"; + String providerEndpoint = "http://localhost:8090/provider"; + String service = tenantDomain + "." + tenantService; + + // Should FAIL validate() as we are passing null for the tenant role action list. + try { + setupTenantDomainProviderService(domainName, tenantDomain, tenantService, providerEndpoint); + + Tenancy tenant = createTenantObject(domainName, service); + ProviderMockClient.setReturnTenantRoles(true); + zms.putTenancy(mockDomRsrcCtx, domainName, service, auditRef, tenant); + + zms.putTenantRoles(mockDomRsrcCtx, tenantDomain, tenantService, domainName, auditRef, null); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } finally { + zms.deleteTenancy(mockDomRsrcCtx, domainName, service, auditRef); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + } + + // Should FAIL validate() as we are passing an empty tenantroles. + try { + setupTenantDomainProviderService(domainName, tenantDomain, tenantService, providerEndpoint); + + Tenancy tenant = createTenantObject(domainName, service); + ProviderMockClient.setReturnTenantRoles(true); + zms.putTenancy(mockDomRsrcCtx, domainName, service, auditRef, tenant); + + TenantRoles tenantRoles = new TenantRoles(); + + zms.putTenantRoles(mockDomRsrcCtx, tenantDomain, tenantService, domainName, auditRef, tenantRoles); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } finally { + zms.deleteTenancy(mockDomRsrcCtx, domainName, service, auditRef); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + } + + // Should FAIL as we are passing a tenantrole with empty actions list. + try { + setupTenantDomainProviderService(domainName, tenantDomain, tenantService, providerEndpoint); + + Tenancy tenant = createTenantObject(domainName, service); + ProviderMockClient.setReturnTenantRoles(true); + zms.putTenancy(mockDomRsrcCtx, domainName, service, auditRef, tenant); + + List roleActions = new ArrayList(); + TenantRoles tenantRoles = new TenantRoles().setDomain(tenantDomain) + .setService(tenantService).setTenant(domainName).setRoles(roleActions); + + zms.putTenantRoles(mockDomRsrcCtx, tenantDomain, tenantService, domainName, auditRef, tenantRoles); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } finally { + zms.deleteTenancy(mockDomRsrcCtx, domainName, service, auditRef); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + } + } + + @Test + public void testPutTenantRolesWithResourceGroup() { + + String domain = "testPutTenantRoles"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + String serviceName = "storage"; + String tenantDomain = "tenantTestPutTenantRoles"; + String resourceGroup = "Group1"; + + TenantResourceGroupRoles tenantRoles = new TenantResourceGroupRoles().setDomain(domain) + .setService(serviceName).setTenant(tenantDomain) + .setRoles(roleActions).setResourceGroup(resourceGroup); + zms.putTenantResourceGroupRoles(mockDomRsrcCtx, domain, serviceName, tenantDomain, resourceGroup, + auditRef, tenantRoles); + + TenantResourceGroupRoles tRoles = zms.getTenantResourceGroupRoles(mockDomRsrcCtx, domain, serviceName, + tenantDomain, resourceGroup); + assertNotNull(tRoles); + assertEquals(domain.toLowerCase(), tRoles.getDomain()); + assertEquals(serviceName.toLowerCase(), tRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), tRoles.getTenant()); + assertEquals(resourceGroup.toLowerCase(), tRoles.getResourceGroup()); + assertEquals(TABLE_PROVIDER_ROLE_ACTIONS.size(), tRoles.getRoles().size()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + + @Test + public void testGetDomainDataCheck() { + + String tenantDomainName = "testGetDomainDataCheck"; + TopLevelDomain tenDom = createTopLevelDomainObject(tenantDomainName, + "Test Provider Domain", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, tenDom); + // create roles + Role role1 = createRoleObject(tenantDomainName, "Role1", null, "user.joe", "user.jane"); + zms.putRole(mockDomRsrcCtx, tenantDomainName, "Role1", auditRef, role1); + + Role role2 = createRoleObject(tenantDomainName, "Role2", null, "user.phil", "user.gil"); + zms.putRole(mockDomRsrcCtx, tenantDomainName, "Role2", auditRef, role2); + + // create policies + Policy policy1 = createPolicyObject(tenantDomainName, "Policy1", "Role1", + "UPDATE", tenantDomainName + ":resource1", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, tenantDomainName, "Policy1", auditRef, policy1); + Policy policy2 = createPolicyObject(tenantDomainName, "Policy2", "Role2", + "READ", tenantDomainName + ":resource1", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, tenantDomainName, "Policy2", auditRef, policy2); + // + // test valid setup domain + DomainDataCheck ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(3, ddc.getPolicyCount()); + assertEquals(3, ddc.getAssertionCount()); + assertEquals(0, ddc.getRoleWildCardCount()); + assertNull(ddc.getDanglingRoles()); + assertNull(ddc.getDanglingPolicies()); + assertNull(ddc.getProvidersWithoutTrust()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // set valid wildcard role + Assertion assertion = new Assertion(); + assertion.setAction("MANAGE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource(tenantDomainName + ":wildlife"); + assertion.setRole(tenantDomainName + ":role.Role*"); + + Policy policy = zms.getPolicy(mockDomRsrcCtx, tenantDomainName, "Policy2"); + List assertList = policy.getAssertions(); + assertList.add(assertion); + policy.setAssertions(assertList); + zms.putPolicy(mockDomRsrcCtx, tenantDomainName, "Policy2", auditRef, policy); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(3, ddc.getPolicyCount()); + assertEquals(4, ddc.getAssertionCount()); + assertEquals(1, ddc.getRoleWildCardCount()); + assertNull(ddc.getDanglingRoles()); + assertNull(ddc.getDanglingPolicies()); + assertNull(ddc.getProvidersWithoutTrust()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // test dangling policy with wildcard role + assertion = new Assertion(); + assertion.setAction("MANAGE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource(tenantDomainName + ":wildlife"); + assertion.setRole(tenantDomainName + ":role.Wild*"); + + policy = zms.getPolicy(mockDomRsrcCtx, tenantDomainName, "Policy2"); + assertList = policy.getAssertions(); + assertList.add(assertion); + policy.setAssertions(assertList); + zms.putPolicy(mockDomRsrcCtx, tenantDomainName, "Policy2", auditRef, policy); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(3, ddc.getPolicyCount()); + assertEquals(5, ddc.getAssertionCount()); + assertEquals(2, ddc.getRoleWildCardCount()); + assertNull(ddc.getDanglingRoles()); + assertEquals(1, ddc.getDanglingPolicies().size()); + assertNull(ddc.getProvidersWithoutTrust()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // add a dangling role + Role role3 = createRoleObject(tenantDomainName, "Role3", null, "user.user1", "user.user3"); + zms.putRole(mockDomRsrcCtx, tenantDomainName, "Role3", auditRef, role3); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(3, ddc.getPolicyCount()); + assertEquals(5, ddc.getAssertionCount()); + assertEquals(2, ddc.getRoleWildCardCount()); + assertEquals(1, ddc.getDanglingRoles().size()); + assertEquals(1, ddc.getDanglingPolicies().size()); + assertNull(ddc.getProvidersWithoutTrust()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // test more dangling policies + // create policy with assertion using unknown role + assertion = new Assertion(); + assertion.setAction("snorkel"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource(tenantDomainName + ":molokoni"); + assertion.setRole(tenantDomainName + ":role.snorkeler"); + + policy = zms.getPolicy(mockDomRsrcCtx, tenantDomainName, "Policy2"); + assertList = policy.getAssertions(); + assertList.add(assertion); + policy.setAssertions(assertList); + zms.putPolicy(mockDomRsrcCtx, tenantDomainName, "Policy2", auditRef, policy); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(3, ddc.getPolicyCount()); + assertEquals(6, ddc.getAssertionCount()); + assertEquals(2, ddc.getRoleWildCardCount()); + assertEquals(1, ddc.getDanglingRoles().size()); + assertEquals(2, ddc.getDanglingPolicies().size()); + assertNull(ddc.getProvidersWithoutTrust()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // create provider domain + String provDomainTop = "testGetDomainDataCheckProvider"; + TopLevelDomain provDom = createTopLevelDomainObject(provDomainTop, + "Test Provider Domain", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, provDom); + + String provDomainSub = provDomainTop + ".sub"; + SubDomain subDom = createSubDomainObject("sub", provDomainTop, null, null, adminUser); + subDom.setAuditEnabled(true); + zms.postSubDomain(mockDomRsrcCtx, provDomainTop, auditRef, subDom); + + // test incomplete tenancy setup + // put tenancy for provider + String provEndPoint = "http://localhost:8090/provider"; + String provSvc = "storage"; + ServiceIdentity service = createServiceObject( + provDomainSub, provSvc, provEndPoint, + "/usr/bin/java", "root", "users", "localhost"); + + zms.putServiceIdentity(mockDomRsrcCtx, provDomainSub, provSvc, auditRef, service); + + ProviderMockClient.setReturnTenantRoles(true); + Tenancy tenant = createTenantObject(tenantDomainName, provDomainSub + "." + provSvc); + zms.putTenancy(mockDomRsrcCtx, tenantDomainName, provDomainSub + "." + provSvc, auditRef, tenant); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(6, ddc.getPolicyCount()); + assertEquals(12, ddc.getAssertionCount()); + assertEquals(2, ddc.getRoleWildCardCount()); + assertEquals(1, ddc.getDanglingRoles().size()); + assertEquals(2, ddc.getDanglingPolicies().size()); + assertTrue(ddc.getDanglingRoles().contains("role3")); + boolean danglingPolicy1Found = false; + boolean danglingPolicy2Found = false; + for (DanglingPolicy danglingPolicy : ddc.getDanglingPolicies()) { + if (danglingPolicy.getPolicyName().equals("policy2") && danglingPolicy.getRoleName().equals("wild*")) { + danglingPolicy1Found = true; + } else if (danglingPolicy.getPolicyName().equals("policy2") && danglingPolicy.getRoleName().equals("snorkeler")) { + danglingPolicy2Found = true; + } + } + assertTrue(danglingPolicy1Found); + assertTrue(danglingPolicy2Found); + assertEquals(1, ddc.getProvidersWithoutTrust().size()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // test that now all is hunky dory between the tenant and provider + // provider gets the trust role(s) + List roleActions = new ArrayList(); + for (Struct.Field f : TABLE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction((String) f.value())); + } + + TenantRoles tenantRoles = new TenantRoles().setDomain(provDomainSub) + .setService(provSvc).setTenant(tenantDomainName) + .setRoles(roleActions); + + zms.putTenantRoles(mockDomRsrcCtx, provDomainSub, provSvc, tenantDomainName, + auditRef, tenantRoles); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(6, ddc.getPolicyCount()); + assertEquals(12, ddc.getAssertionCount()); + assertEquals(2, ddc.getRoleWildCardCount()); + assertEquals(1, ddc.getDanglingRoles().size()); + assertEquals(2, ddc.getDanglingPolicies().size()); + assertNull(ddc.getProvidersWithoutTrust()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, provDomainSub); + assertNotNull(ddc); + assertEquals(4, ddc.getPolicyCount()); + assertEquals(4, ddc.getAssertionCount()); + assertEquals(0, ddc.getRoleWildCardCount()); + assertNull(ddc.getDanglingRoles()); + assertNull(ddc.getDanglingPolicies()); + assertNull(ddc.getProvidersWithoutTrust()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // test provider should report tenant is missing + // remove the assume_role policies from the tenant + zms.deleteTenancy(mockDomRsrcCtx, tenantDomainName, provDomainSub + "." + provSvc, auditRef); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, provDomainSub); + assertNotNull(ddc); + assertEquals(4, ddc.getPolicyCount()); + assertEquals(4, ddc.getAssertionCount()); + assertEquals(0, ddc.getRoleWildCardCount()); + assertNull(ddc.getDanglingRoles()); + assertNull(ddc.getDanglingPolicies()); + assertNull(ddc.getProvidersWithoutTrust()); + assertTrue(ddc.getTenantsWithoutAssumeRole().size() == 1); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(3, ddc.getPolicyCount()); + assertEquals(6, ddc.getAssertionCount()); + assertEquals(2, ddc.getRoleWildCardCount()); + assertEquals(2, ddc.getDanglingRoles().size()); + assertEquals(2, ddc.getDanglingPolicies().size()); + assertNull(ddc.getProvidersWithoutTrust()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // test service name with resource group + // setup up the top level domain+service with resource group + String provSvcTop = "shelter"; + service = createServiceObject( + provDomainTop, provSvcTop, provEndPoint, + "/usr/bin/java", "root", "users", "localhost"); + + zms.putServiceIdentity(mockDomRsrcCtx, provDomainTop, provSvcTop, auditRef, service); + + TenantResourceGroupRoles tenantGroupRoles = new TenantResourceGroupRoles() + .setDomain(provDomainTop) + .setService(provSvcTop).setTenant(tenantDomainName) + .setRoles(roleActions).setResourceGroup("ravers"); + // put the trust roles with resource group into top level provider domain + // - tenant is not yet supporting the top level domain + zms.putTenantResourceGroupRoles(mockDomRsrcCtx, provDomainTop, provSvcTop, tenantDomainName, "ravers", + auditRef, tenantGroupRoles); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, provDomainTop); + assertNotNull(ddc); + assertEquals(4, ddc.getPolicyCount()); + assertEquals(4, ddc.getAssertionCount()); + assertEquals(0, ddc.getRoleWildCardCount()); + assertNull(ddc.getDanglingRoles()); + assertNull(ddc.getDanglingPolicies()); + assertNull(ddc.getProvidersWithoutTrust()); + assertEquals(1, ddc.getTenantsWithoutAssumeRole().size()); + + // now set up the tenant for the sub domain provider + ProviderResourceGroupRoles providerRoles = new ProviderResourceGroupRoles() + .setDomain(provDomainSub).setService(provSvc) + .setTenant(tenantDomainName).setRoles(roleActions) + .setResourceGroup("ravers"); + // this sets up the assume roles in the tenant for the sub domain + // if it is an authorized service, then it will setup the provider roles too + zms.putProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomainName, provDomainSub, provSvc, + "ravers", auditRef, providerRoles); + + // tenant sees that the subdomain provider isnt provisioned yet + // for the resource group: testgetdomaindatacheckprovider.sub.storage.ravers + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc.toString(), ddc); + assertEquals(7, ddc.getPolicyCount()); + assertEquals(12, ddc.getAssertionCount()); + assertEquals(2, ddc.getRoleWildCardCount()); + assertEquals(1, ddc.getDanglingRoles().size()); + assertEquals(2, ddc.getDanglingPolicies().size()); + assertEquals(1, ddc.getProvidersWithoutTrust().size()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // setup tenancy in the tenant domain for the provider subdomain + zms.putTenancy(mockDomRsrcCtx, tenantDomainName, provDomainSub + "." + provSvc, auditRef, tenant); + + // the subdomain provider believes it is in sync with tenant + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, provDomainSub); + assertNotNull(ddc); + assertEquals(4, ddc.getPolicyCount()); + assertEquals(4, ddc.getAssertionCount()); + assertEquals(0, ddc.getRoleWildCardCount()); + assertNull(ddc.getDanglingRoles()); + assertNull(ddc.getDanglingPolicies()); + assertNull(ddc.getProvidersWithoutTrust()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // but the tenant sees the sub provider is not setup + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(9, ddc.getPolicyCount()); + assertEquals(15, ddc.getAssertionCount()); + assertEquals(2, ddc.getRoleWildCardCount()); + assertEquals(1, ddc.getDanglingRoles().size()); + assertEquals(2, ddc.getDanglingPolicies().size()); + assertEquals(1, ddc.getProvidersWithoutTrust().size()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // now set up the sub domain provider for the tenant with resource groups + // so tenant and the sub domain provider are in sync again + // add resource groups to provider + tenantGroupRoles = new TenantResourceGroupRoles() + .setDomain(provDomainSub) + .setService(provSvc).setTenant(tenantDomainName) + .setRoles(roleActions).setResourceGroup("ravers"); + // put the trust roles into sub domain provider + zms.putTenantResourceGroupRoles(mockDomRsrcCtx, provDomainSub, provSvc, tenantDomainName, "ravers", + auditRef, tenantGroupRoles); + + // now tenant sees the sub domain has provisioned it + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(9, ddc.getPolicyCount()); + assertEquals(15, ddc.getAssertionCount()); + assertEquals(2, ddc.getRoleWildCardCount()); + assertEquals(1, ddc.getDanglingRoles().size()); + assertEquals(2, ddc.getDanglingPolicies().size()); + assertNull(ddc.getProvidersWithoutTrust()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // now set up the tenant for the top level domain provider + // so tenant and the top level domain provider are in sync again + providerRoles = new ProviderResourceGroupRoles() + .setDomain(provDomainTop).setService(provSvcTop) + .setTenant(tenantDomainName).setRoles(roleActions) + .setResourceGroup("ravers"); + // this sets up the assume roles in the tenant for the top level domain + zms.putProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomainName, provDomainTop, provSvcTop, + "ravers", auditRef, providerRoles); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(13, ddc.getPolicyCount()); + assertEquals(21, ddc.getAssertionCount()); + assertEquals(2, ddc.getRoleWildCardCount()); + assertEquals(1, ddc.getDanglingRoles().size()); + assertEquals(2, ddc.getDanglingPolicies().size()); + assertEquals(1, ddc.getProvidersWithoutTrust().size()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // delete the resource group tenancy support from sub domain + // this means the tenant domain should show both the sub domain and + // the top domain is without trust roles + zms.deleteTenantResourceGroupRoles(mockDomRsrcCtx, provDomainSub, provSvc, + tenantDomainName, "ravers", auditRef); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(13, ddc.getPolicyCount()); + assertEquals(21, ddc.getAssertionCount()); + assertEquals(2, ddc.getRoleWildCardCount()); + assertEquals(1, ddc.getDanglingRoles().size()); + assertEquals(2, ddc.getDanglingPolicies().size()); + assertEquals(2, ddc.getProvidersWithoutTrust().size()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // delete the dangling policies and dangling role + zms.deletePolicy(mockDomRsrcCtx, tenantDomainName, "Policy2", auditRef); + zms.deleteRole(mockDomRsrcCtx, tenantDomainName, "Role3", auditRef); + zms.deleteRole(mockDomRsrcCtx, tenantDomainName, "Role2", auditRef); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(12, ddc.getPolicyCount()); + assertEquals(17, ddc.getAssertionCount()); + assertEquals(0, ddc.getRoleWildCardCount()); + assertNull(ddc.getDanglingRoles()); + assertNull(ddc.getDanglingPolicies()); + assertEquals(2, ddc.getProvidersWithoutTrust().size()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // add the tenancy support for top domain + // - now tenant will see that it is all setup + tenantRoles = new TenantRoles().setDomain(provDomainTop) + .setService(provSvcTop).setTenant(tenantDomainName) + .setRoles(roleActions); + + zms.putTenantRoles(mockDomRsrcCtx, provDomainTop, provSvcTop, tenantDomainName, + auditRef, tenantRoles); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(12, ddc.getPolicyCount()); + assertEquals(17, ddc.getAssertionCount()); + assertEquals(0, ddc.getRoleWildCardCount()); + assertNull(ddc.getDanglingRoles()); + assertNull(ddc.getDanglingPolicies()); + assertEquals(1, ddc.getProvidersWithoutTrust().size()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + // delete the provider resource group roles for the sub domain provider + // then everything in sync for this tenant + zms.deleteProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomainName, provDomainSub, provSvc, + "ravers", auditRef); + + ddc = zms.getDomainDataCheck(mockDomRsrcCtx, tenantDomainName); + assertNotNull(ddc); + assertEquals(9, ddc.getPolicyCount()); + assertEquals(14, ddc.getAssertionCount()); + assertEquals(0, ddc.getRoleWildCardCount()); + assertNull(ddc.getDanglingRoles()); + assertNull(ddc.getDanglingPolicies()); + assertNull(ddc.getProvidersWithoutTrust()); + assertNull(ddc.getTenantsWithoutAssumeRole()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomainName, auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, provDomainTop, "sub", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, provDomainTop, auditRef); + } + + @Test + public void testGetServicePrincipal() { + + Authority principalAuthority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + SimpleServiceIdentityProvider provider = new SimpleServiceIdentityProvider(principalAuthority, privKey); + + Principal testPrincipal = provider.getIdentity("coretech", "storage"); + assertNotNull(testPrincipal); + ResourceContext rsrcCtxTest = createResourceContext(testPrincipal); + ServicePrincipal principal = zms.getServicePrincipal(rsrcCtxTest); + assertNotNull(principal); + assertTrue(principal.getService().equals("storage")); + assertTrue(principal.getDomain().equals("coretech")); + } + + @Test + public void testEmitMonmetricError() { + int errorCode = 403; + String caller = "forbiddenError"; + boolean isEmitMonmetricError; + + // negative tests + isEmitMonmetricError = ZMSUtils.emitMonmetricError(errorCode, null); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZMSUtils.emitMonmetricError(errorCode, ""); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZMSUtils.emitMonmetricError(errorCode, new String()); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZMSUtils.emitMonmetricError(errorCode, "invalidcharacterslike...$!?"); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZMSUtils.emitMonmetricError(errorCode, "spaces are not allowed"); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZMSUtils.emitMonmetricError(0, caller); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZMSUtils.emitMonmetricError(-100, caller); + assertFalse(isEmitMonmetricError); + + // positive tests + isEmitMonmetricError = ZMSUtils.emitMonmetricError(errorCode, caller); + assertTrue(isEmitMonmetricError); + + isEmitMonmetricError = ZMSUtils.emitMonmetricError(errorCode, " " + caller + " "); + assertTrue(isEmitMonmetricError); + } + + @Test + public void testCheckKerberosAuthorityAuthorization() { + Authority authority = new com.yahoo.athenz.auth.impl.KerberosAuthority(); + Principal principal = SimplePrincipal.create("krb", "user1", "v=U1;d=user;n=user1;s=signature", + 0, authority); + assertTrue(zms.authorityAuthorizationAllowed(principal)); + } + + @Test + public void testCheckNullAuthorityAuthorization() { + Principal principal = SimplePrincipal.create("user", "joe", "v=U1;d=user;n=user1;s=signature", + 0, null); + assertTrue(zms.authorityAuthorizationAllowed(principal)); + } + + @Test + public void testValidRoleTokenAccessTrustDomain() { + assertFalse(zms.validRoleTokenAccess("TrustDomain", "Domain1", "Domain1")); + } + + @Test + public void testValidRoleTokenAccessMismatchYRNs() { + assertFalse(zms.validRoleTokenAccess(null, "Domain1", "Domain2")); + } + + @Test + public void testValidRoleTokenAccessValid() { + assertTrue(zms.validRoleTokenAccess(null, "Domain1", "Domain1")); + } + + @Test + public void testIsVirtualDomain() { + + assertTrue(zms.isVirtualDomain("user.user1")); + assertTrue(zms.isVirtualDomain("user.user2")); + assertTrue(zms.isVirtualDomain("user.user1.sub1")); + assertTrue(zms.isVirtualDomain("user.user1.sub2.sub3")); + + assertFalse(zms.isVirtualDomain("user")); + assertFalse(zms.isVirtualDomain("usertest")); + assertFalse(zms.isVirtualDomain("coretech.api")); + } + + @Test + public void testHasExceededVirtualSubDomainLimitUnderLimitOneLevel() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT, "2"); + ZMSImpl zmsTest = zmsInit(); + + assertFalse(zmsTest.hasExceededVirtualSubDomainLimit("user.user1")); + + SubDomain dom = createSubDomainObject("sub1", "user.user1", + "Test Domain2", "testOrg", adminUser); + Domain resDom = zms.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + assertFalse(zmsTest.hasExceededVirtualSubDomainLimit("user.user1")); + + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub1", auditRef); + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT); + } + + @Test + public void testHasExceededVirtualSubDomainLimitOverLimitOneLevel() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT, "2"); + ZMSImpl zmsTest = zmsInit(); + + SubDomain dom = createSubDomainObject("sub1", "user.user1", + "Test Domain2", "testOrg", adminUser); + Domain resDom = zms.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub2", "user.user1", + "Test Domain2", "testOrg", adminUser); + resDom = zms.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + assertTrue(zmsTest.hasExceededVirtualSubDomainLimit("user.user1")); + + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub1", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub2", auditRef); + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT); + } + + @Test + public void testHasExceededVirtualSubDomainLimitUnderLimitMultipleLevel() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT, "3"); + ZMSImpl zmsTest = zmsInit(); + + assertFalse(zmsTest.hasExceededVirtualSubDomainLimit("user.user1")); + + SubDomain dom = createSubDomainObject("sub1", "user.user1", + "Test Domain2", "testOrg", adminUser); + Domain resDom = zms.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub2", "user.user1.sub1", + "Test Domain2", "testOrg", adminUser); + resDom = zms.postSubDomain(mockDomRsrcCtx, "user.user1.sub1", auditRef, dom); + assertNotNull(resDom); + + assertFalse(zmsTest.hasExceededVirtualSubDomainLimit("user.user1.sub1")); + + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1.sub1", "sub2", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub1", auditRef); + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT); + } + + @Test + public void testHasExceededVirtualSubDomainLimitOverLimitMultipleLevel() { + + System.setProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT, "2"); + ZMSImpl zmsTest = zmsInit(); + + SubDomain dom = createSubDomainObject("sub1", "user.user1", + "Test Domain2", "testOrg", adminUser); + Domain resDom = zms.postSubDomain(mockDomRsrcCtx, "user.user1", auditRef, dom); + assertNotNull(resDom); + + dom = createSubDomainObject("sub2", "user.user1.sub1", + "Test Domain2", "testOrg", adminUser); + resDom = zms.postSubDomain(mockDomRsrcCtx, "user.user1.sub1", auditRef, dom); + assertNotNull(resDom); + + assertTrue(zmsTest.hasExceededVirtualSubDomainLimit("user.user1.sub1")); + assertTrue(zmsTest.hasExceededVirtualSubDomainLimit("user.user1")); + + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1.sub1", "sub2", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "user.user1", "sub1", auditRef); + System.clearProperty(ZMSConsts.ZMS_PROP_VIRTUAL_DOMAIN_LIMIT); + } + + @Test + public void testGetNormalizedMemberNoSplit() { + + assertEquals(zms.getNormalizedMember("user.user"), "user.user"); + assertEquals(zms.getNormalizedMember("user.user2"), "user.user2"); + assertEquals(zms.getNormalizedMember("user.user1"), "user.user1"); + assertEquals(zms.getNormalizedMember("coretech.storage"), "coretech.storage"); + assertEquals(zms.getNormalizedMember("user1"), "user1"); + } + + @Test + public void testGetNormalizedMemberInvalidFormat() { + + assertEquals(zms.getNormalizedMember("user:user:user1"), "user:user:user1"); + assertEquals(zms.getNormalizedMember("user:"), "user:"); + assertEquals(zms.getNormalizedMember("coretech:storage:api"), "coretech:storage:api"); + } + + @Test + public void testGetNormalizedMemberUsersWithSplit() { + + assertEquals(zms.getNormalizedMember("user:user"), "user.user"); + assertEquals(zms.getNormalizedMember("user:user2"), "user.user2"); + assertEquals(zms.getNormalizedMember("user:user1"), "user.user1"); + } + + @Test + public void testGetNormalizedMemberServiceWithSplit() { + + assertEquals(zms.getNormalizedMember("coretech:service.storage"), "coretech.storage"); + assertEquals(zms.getNormalizedMember("weather:service.storage.api"), "weather.storage.api"); + assertEquals(zms.getNormalizedMember("weather.storage:service.api"), "weather.storage.api"); + assertEquals(zms.getNormalizedMember("weather.storage:entity.api"), "weather.storage:entity.api"); + assertEquals(zms.getNormalizedMember("weather.storage:service."), "weather.storage."); + } + + @Test + public void testNormalizeRoleMembersUsers() { + + ArrayList roleMembers = new ArrayList<>(); + roleMembers.add("user:joe"); + roleMembers.add("user.joe"); + roleMembers.add("user:joe"); + roleMembers.add("user.jane"); + + Role role = createRoleObject("TestRole", "Role1", null, roleMembers); + zms.normalizeRoleMembers(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 2); + assertTrue(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + } + + @Test + public void testNormalizeRoleMembersServices() { + + ArrayList roleMembers = new ArrayList<>(); + roleMembers.add("coretech.storage"); + roleMembers.add("coretech:service.storage"); + roleMembers.add("weather:service.storage"); + + Role role = createRoleObject("TestRole", "Role1", null, roleMembers); + zms.normalizeRoleMembers(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 2); + assertTrue(members.contains("coretech.storage")); + assertTrue(members.contains("weather.storage")); + } + + @Test + public void testNormalizeRoleMembersCombined() { + + ArrayList roleMembers = new ArrayList<>(); + roleMembers.add("user:joe"); + roleMembers.add("user.joe"); + roleMembers.add("user:joe"); + roleMembers.add("user.jane"); + roleMembers.add("coretech.storage"); + roleMembers.add("coretech:service.storage"); + roleMembers.add("weather:service.storage"); + roleMembers.add("weather.api.access"); + + Role role = createRoleObject("TestRole", "Role1", null, roleMembers); + zms.normalizeRoleMembers(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 5); + assertTrue(members.contains("user.joe")); + assertTrue(members.contains("user.jane")); + assertTrue(members.contains("weather.api.access")); + assertTrue(members.contains("coretech.storage")); + assertTrue(members.contains("weather.storage")); + } + + @Test + public void testNormalizeRoleMembersInvalid() { + + ArrayList roleMembers = new ArrayList<>(); + roleMembers.add("user.joe"); + roleMembers.add("user2"); + + Role role = createRoleObject("TestRole", "Role1", null, roleMembers); + zms.normalizeRoleMembers(role); + + List members = role.getMembers(); + assertNotNull(members); + assertEquals(members.size(), 2); + assertTrue(members.contains("user.joe")); + assertTrue(members.contains("user2")); + } + + @Test + public void testHasExceededListLimitNullLimit() { + assertFalse(zms.hasExceededListLimit(null, 10)); + } + + @Test + public void testHasExceededListLimitNotValidLimit() { + assertFalse(zms.hasExceededListLimit(0, 10)); + assertFalse(zms.hasExceededListLimit(-1, 10)); + } + + @Test + public void testHasExceededListLimitYes() { + assertTrue(zms.hasExceededListLimit(10, 11)); + } + + @Test + public void testHasExceededListLimitNo() { + assertFalse(zms.hasExceededListLimit(10, 9)); + assertFalse(zms.hasExceededListLimit(10, 10)); + } + + @Test + public void testVerifyServicePublicKeysNoKeys() { + + ServiceIdentity service = new ServiceIdentity(); + service.setName(ZMSUtils.serviceResourceName("ServiceAddInvalidCertDom1", "Service1")); + assertFalse(zms.verifyServicePublicKeys(service)); + } + + @Test + public void testVerifyServicePublicKeysInvalidPublicKeys() { + + ServiceIdentity service = new ServiceIdentity(); + service.setName(ZMSUtils.serviceResourceName("ServiceDom1", "Service1")); + + List publicKeyList = new ArrayList(); + PublicKeyEntry publicKeyEntry1 = new PublicKeyEntry(); + publicKeyEntry1.setKey("LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTk"); + publicKeyEntry1.setId("1"); + publicKeyList.add(publicKeyEntry1); + service.setPublicKeys(publicKeyList); + + assertFalse(zms.verifyServicePublicKeys(service)); + } + + @Test + public void testVerifyServicePublicKeyInvalidPublicKey() { + assertFalse(zms.verifyServicePublicKey("LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1B")); + assertFalse(zms.verifyServicePublicKey("LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTk")); + assertFalse(zms.verifyServicePublicKey(privKeyK1)); + assertFalse(zms.verifyServicePublicKey(privKeyK2)); + } + + @Test + public void testVerifyServicePublicKeyValidPublicKey() { + assertTrue(zms.verifyServicePublicKey(pubKeyK1)); + assertTrue(zms.verifyServicePublicKey(pubKeyK2)); + } + + @Test + public void testVerifyServicePublicKeysValidKeysOnly() { + ServiceIdentity service = createServiceObject("ServiceAddDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + assertTrue(zms.verifyServicePublicKeys(service)); + } + + @Test + public void testShouldRunDelegatedTrustCheckNullTrust() { + assertFalse(zms.shouldRunDelegatedTrustCheck(null, "TrustDomain")); + } + @Test + public void testShouldRunDelegatedTrustCheckNullTrustDomain() { + assertTrue(zms.shouldRunDelegatedTrustCheck("TrustDomain", null)); + } + @Test + public void testShouldRunDelegatedTrustCheckMatch() { + assertTrue(zms.shouldRunDelegatedTrustCheck("TrustDomain", "TrustDomain")); + } + @Test + public void testShouldRunDelegatedTrustCheckNoMatch() { + assertFalse(zms.shouldRunDelegatedTrustCheck("TrustDomain1", "TrustDomain")); + } + + @Test + public void testIsValidUserTokenRequestNoAuthority() { + Principal principal = SimplePrincipal.create("user", "user1", "v=U1;d=user;n=user1;s=signature"); + assertFalse(zms.isValidUserTokenRequest(principal, "user1")); + } + + @Test + public void testIsValidUserTokenRequestNotuserAuthority() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal = SimplePrincipal.create("user", "user1", "v=U1;d=user;n=user1;s=signature", + 0, principalAuthority); + + assertFalse(zms.isValidUserTokenRequest(principal, "user1")); + } + + @Test + public void testIsValidUserTokenRequestNullPrincipal() { + assertFalse(zms.isValidUserTokenRequest(null, "user1")); + } + + @Test + public void testMatchDelegatedTrustPolicyNullAssertions() { + Policy policy = new Policy(); + assertFalse(zms.matchDelegatedTrustPolicy(policy, "testRole", "testMember", null)); + } + + @Test + public void testMatchDelegatedTrustAssertionInvalidAction() { + + Assertion assertion = new Assertion(); + assertion.setAction("READ"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("domain:*"); + assertion.setRole("domain:role.Role"); + + assertFalse(zms.matchDelegatedTrustAssertion(assertion, null, null, null)); + } + + @Test + public void testMatchDelegatedTrustAssertionNoResPatternMatchWithOutPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("domain:role.Role"); + assertion.setRole("domain:role.Role"); + + assertFalse(zms.matchDelegatedTrustAssertion(assertion, "domain:role.Role2", null, null)); + assertFalse(zms.matchDelegatedTrustAssertion(assertion, "coretech:role.Role", null, null)); + } + + @Test + public void testMatchDelegatedTrustAssertionNoResPatternMatchWithPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("domain:role.Role"); + + assertFalse(zms.matchDelegatedTrustAssertion(assertion, "domain:role.Role2", null, null)); + assertFalse(zms.matchDelegatedTrustAssertion(assertion, "coretech:role.Role2", null, null)); + } + + @Test + public void testMatchDelegatedTrustAssertionNoRoleMatchWithPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("weather:role.*"); + + Role role = null; + List roles = new ArrayList<>(); + + role = createRoleObject("coretech", "readers", null); + roles.add(role); + + role = createRoleObject("coretech", "writers", null); + roles.add(role); + + role = createRoleObject("coretech", "updaters", null); + roles.add(role); + + assertFalse(zms.matchDelegatedTrustAssertion(assertion, "coretech:role.Role", null, roles)); + } + + @Test + public void testMatchDelegatedTrustAssertionNoRoleMatchWithOutPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("weather:role.Role"); + + Role role = null; + List roles = new ArrayList<>(); + + role = createRoleObject("coretech", "Role1", null); + roles.add(role); + + role = createRoleObject("coretech", "Role2", null); + roles.add(role); + + assertFalse(zms.matchDelegatedTrustAssertion(assertion, "weather:role.Role1", null, roles)); + assertFalse(zms.matchDelegatedTrustAssertion(assertion, "coretech:role.Role", null, roles)); + } + + @Test + public void testMatchDelegatedTrustAssertionNoMemberMatch() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("weather:role.Role"); + + Role role = null; + List roles = new ArrayList<>(); + + role = createRoleObject("weather", "Role1", null, "user.user1", null); + roles.add(role); + + role = createRoleObject("weather", "Role", null, "user.user2", null); + roles.add(role); + + assertFalse(zms.matchDelegatedTrustAssertion(assertion, "weather:role.Role", "user.user1", roles)); + } + + @Test + public void testMatchDelegatedTrustAssertionValidWithPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("weather:role.*"); + + Role role = null; + List roles = new ArrayList<>(); + + role = createRoleObject("weather", "Role1", null, "user.user1", null); + roles.add(role); + + role = createRoleObject("weather", "Role", null, "user.user2", null); + roles.add(role); + + assertTrue(zms.matchDelegatedTrustAssertion(assertion, "weather:role.Role", "user.user2", roles)); + } + + @Test + public void testMatchDelegatedTrustAssertionValidWithOutPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("weather:role.Role"); + + Role role = null; + List roles = new ArrayList<>(); + + role = createRoleObject("weather", "Role1", null, "user.user1", null); + roles.add(role); + + role = createRoleObject("weather", "Role", null, "user.user2", null); + roles.add(role); + + assertTrue(zms.matchDelegatedTrustAssertion(assertion, "weather:role.Role", "user.user2", roles)); + } + + @Test + public void testMatchPrincipalInRoleStdMemberMatch() { + + Role role = createRoleObject("weather", "Role", null, "user.user2", null); + assertTrue(zms.matchPrincipalInRole(role, null, "user.user2", null)); + } + + @Test + public void testMatchPrincipalInRoleStdMemberNoMatch() { + + Role role = createRoleObject("weather", "Role", null, "user.user2", null); + assertFalse(zms.matchPrincipalInRole(role, null, "user.user23", null)); + } + + @Test + public void testMatchPrincipalInRoleNoDelegatedTrust() { + Role role = createRoleObject("weather", "Role", null); + assertFalse(zms.matchPrincipalInRole(role, null, null, null)); + assertFalse(zms.matchPrincipalInRole(role, null, null, "weather")); + } + + @Test + public void testMatchPrincipalInRoleDelegatedTrustNoMatch() { + Role role = createRoleObject("weather", "Role", "coretech_not_present"); + assertFalse(zms.matchPrincipalInRole(role, "Role", "user.user1", "coretech_not_present")); + } + + @Test + public void testMatchPrincipalInRoleDelegatedTrustMatch() { + + String domainName = "coretechtrust"; + List adminUsers = new ArrayList<>(); + adminUsers.add("user.user2"); + zms.dbService.makeDomain(mockDomRsrcCtx, domainName, "Test Domain", "org", + true, adminUsers, null, 0, null, auditRef); + + Policy policy = createPolicyObject(domainName, "trust", "coretechtrust:role.role1", + false, "ASSUME_ROLE", "weather:role.role1", AssertionEffect.ALLOW); + zms.dbService.executePutPolicy(mockDomRsrcCtx, domainName, "trust", + policy, auditRef, "unitTest"); + + Role role1 = createRoleObject(domainName, "role1", null, "user.user1", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role1", + role1, auditRef, "unittest"); + + Role role2 = createRoleObject(domainName, "role2", null, "user.user2", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role2", + role2, auditRef, "unittest"); + + Role role = createRoleObject("weather", "role1", domainName); + assertTrue(zms.matchPrincipalInRole(role, "weather:role.role1", "user.user1", "coretechtrust")); + assertFalse(zms.matchPrincipalInRole(role, "weather:role.role1", "user.user1", "coretechtrust2")); + assertFalse(zms.matchPrincipalInRole(role, "weather:role.role1", "user.user3", "coretechtrust")); + zms.dbService.executeDeleteDomain(mockDomRsrcCtx, domainName, auditRef, "unittest"); + } + + @Test + public void testProcessListRequestNoCollection() { + + String domainName = "listrequest"; + List adminUsers = new ArrayList<>(); + adminUsers.add("user.user"); + zms.dbService.makeDomain(mockDomRsrcCtx, domainName, "Test Domain", "org", + true, adminUsers, null, 0, null, auditRef); + + Role role1 = createRoleObject(domainName, "role1", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role1", + role1, auditRef, "unittest"); + + Role role2 = createRoleObject(domainName, "role2", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role2", + role2, auditRef, "unittest"); + + Role role3 = createRoleObject(domainName, "role3", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role3", + role3, auditRef, "unittest"); + + zms.dbService.executeDeletePolicy(mockDomRsrcCtx, domainName, "admin", auditRef, "unittest"); + + List names = new ArrayList<>(); + assertNull(zms.processListRequest(domainName, AthenzObject.POLICY, null, null, names)); + assertEquals(names.size(), 0); + zms.dbService.executeDeleteDomain(mockDomRsrcCtx, domainName, auditRef, "unittest"); + } + + @Test + public void testProcessListRequestCollectionEmpty() { + + String domainName = "listrequest"; + List adminUsers = new ArrayList<>(); + adminUsers.add("user.user"); + zms.dbService.makeDomain(mockDomRsrcCtx, domainName, "Test Domain", "org", + true, adminUsers, null, 0, null, auditRef); + + zms.dbService.executeDeleteRole(mockDomRsrcCtx, domainName, "admin", auditRef, "unittest"); + + List names = new ArrayList<>(); + assertNull(zms.processListRequest(domainName, AthenzObject.ROLE, null, null, names)); + assertEquals(names.size(), 0); + zms.dbService.executeDeleteDomain(mockDomRsrcCtx, domainName, auditRef, "unittest"); + } + + @Test + public void testProcessListRequestUnknownType() { + + List names = new ArrayList<>(); + assertNull(zms.processListRequest("testdomain", AthenzObject.ASSERTION, null, null, names)); + assertEquals(names.size(), 0); + } + + @Test + public void testProcessListRequestSkipNoMatch() { + + String domainName = "listrequest"; + List adminUsers = new ArrayList<>(); + adminUsers.add("user.user"); + zms.dbService.makeDomain(mockDomRsrcCtx, domainName, "Test Domain", "org", + true, adminUsers, null, 0, null, auditRef); + + Role role1 = createRoleObject(domainName, "role1", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role1", + role1, auditRef, "unittest"); + + Role role2 = createRoleObject(domainName, "role2", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role2", + role2, auditRef, "unittest"); + + Role role3 = createRoleObject(domainName, "role3", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role3", + role3, auditRef, "unittest"); + + List names = new ArrayList<>(); + assertNull(zms.processListRequest(domainName, AthenzObject.ROLE, null, "role4", names)); + + // our response is going to get the admin role + + assertEquals(4, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("role1")); + assertTrue(names.contains("role2")); + assertTrue(names.contains("role3")); + zms.dbService.executeDeleteDomain(mockDomRsrcCtx, domainName, auditRef, "unittest"); + } + + @Test + public void testProcessListRequestSkipMatch() { + + String domainName = "listrequest"; + List adminUsers = new ArrayList<>(); + adminUsers.add("user.user"); + zms.dbService.makeDomain(mockDomRsrcCtx, domainName, "Test Domain", "org", + true, adminUsers, null, 0, null, auditRef); + + Role role1 = createRoleObject(domainName, "role1", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role1", + role1, auditRef, "unittest"); + + Role role2 = createRoleObject(domainName, "role2", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role2", + role2, auditRef, "unittest"); + + Role role3 = createRoleObject(domainName, "role3", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role3", + role3, auditRef, "unittest"); + + List names = new ArrayList<>(); + assertNull(zms.processListRequest(domainName, AthenzObject.ROLE, null, "role2", names)); + assertEquals(names.size(), 1); + assertTrue(names.contains("role3")); + zms.dbService.executeDeleteDomain(mockDomRsrcCtx, domainName, auditRef, "unittest"); + } + + @Test + public void testProcessListRequestLimitExceeded() { + + String domainName = "listrequest"; + List adminUsers = new ArrayList<>(); + adminUsers.add("user.user"); + zms.dbService.makeDomain(mockDomRsrcCtx, domainName, "Test Domain", "org", + true, adminUsers, null, 0, null, auditRef); + + Role role1 = createRoleObject(domainName, "role1", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role1", + role1, auditRef, "unittest"); + + Role role2 = createRoleObject(domainName, "role2", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role2", + role2, auditRef, "unittest"); + + Role role3 = createRoleObject(domainName, "role3", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role3", + role3, auditRef, "unittest"); + + List names = new ArrayList<>(); + String next = zms.processListRequest(domainName, AthenzObject.ROLE, 2, null, names); + assertEquals("role1", next); + assertEquals(2, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("role1")); + zms.dbService.executeDeleteDomain(mockDomRsrcCtx, domainName, auditRef, "unittest"); + } + + @Test + public void testProcessListRequestLimitNotExceeded() { + + String domainName = "listrequest"; + List adminUsers = new ArrayList<>(); + adminUsers.add("user.user"); + zms.dbService.makeDomain(mockDomRsrcCtx, domainName, "Test Domain", "org", + true, adminUsers, null, 0, null, auditRef); + + Role role1 = createRoleObject(domainName, "role1", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role1", + role1, auditRef, "unittest"); + + Role role2 = createRoleObject(domainName, "role2", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role2", + role2, auditRef, "unittest"); + + Role role3 = createRoleObject(domainName, "role3", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role3", + role3, auditRef, "unittest"); + + List names = new ArrayList<>(); + zms.processListRequest(domainName, AthenzObject.ROLE, 5, null, names); + + // make sure to account for the admin role + + assertEquals(4, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("role1")); + assertTrue(names.contains("role2")); + assertTrue(names.contains("role3")); + zms.dbService.executeDeleteDomain(mockDomRsrcCtx, domainName, auditRef, "unittest"); + } + + @Test + public void testProcessListRequestLimitAndSkip() { + + + String domainName = "listrequest"; + List adminUsers = new ArrayList<>(); + adminUsers.add("user.user"); + zms.dbService.makeDomain(mockDomRsrcCtx, domainName, "Test Domain", "org", + true, adminUsers, null, 0, null, auditRef); + + Role role1 = createRoleObject(domainName, "role1", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role1", + role1, auditRef, "unittest"); + + Role role2 = createRoleObject(domainName, "role2", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role2", + role2, auditRef, "unittest"); + + Role role3 = createRoleObject(domainName, "role3", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role3", + role3, auditRef, "unittest"); + + Role role4 = createRoleObject(domainName, "role4", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role4", + role4, auditRef, "unittest"); + + Role role5 = createRoleObject(domainName, "role5", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role5", + role5, auditRef, "unittest"); + + List names = new ArrayList<>(); + String next = zms.processListRequest(domainName, AthenzObject.ROLE, 2, "role2", names); + assertEquals(next, "role4"); + assertEquals(names.size(), 2); + assertTrue(names.contains("role3")); + assertTrue(names.contains("role4")); + zms.dbService.executeDeleteDomain(mockDomRsrcCtx, domainName, auditRef, "unittest"); + } + + @Test + public void testProcessListRequestLimitAndSkipLessThanLimitLeft() { + + String domainName = "listrequest"; + List adminUsers = new ArrayList<>(); + adminUsers.add("user.user"); + zms.dbService.makeDomain(mockDomRsrcCtx, domainName, "Test Domain", "org", + true, adminUsers, null, 0, null, auditRef); + + Role role1 = createRoleObject(domainName, "role1", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role1", + role1, auditRef, "unittest"); + + Role role2 = createRoleObject(domainName, "role2", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role2", + role2, auditRef, "unittest"); + + Role role3 = createRoleObject(domainName, "role3", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role3", + role3, auditRef, "unittest"); + + Role role4 = createRoleObject(domainName, "role4", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role4", + role4, auditRef, "unittest"); + + Role role5 = createRoleObject(domainName, "role5", null, "user.user", null); + zms.dbService.executePutRole(mockDomRsrcCtx, domainName, "role5", + role5, auditRef, "unittest"); + + List names = new ArrayList<>(); + assertNull(zms.processListRequest(domainName, AthenzObject.ROLE, 2, "role4", names)); + assertEquals(names.size(), 1); + assertTrue(names.contains("role5")); + } + + @Test + public void testAccessInvalidResourceDomain() { + Principal principal = SimplePrincipal.create("user", "user1", "v=U1;d=user;n=user1;s=signature"); + try { + zms.access("read", "domain:invalid:entity", principal, null); + fail(); + } catch (com.yahoo.athenz.common.server.rest.ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testHasAccessInvalidRoleTokenAccess() { + + List authRoles = new ArrayList<>(); + authRoles.add("role1"); + Principal principal = SimplePrincipal.create("coretech", "v=U1;d=user;n=user1;s=signature", authRoles, null); + AthenzDomain domain = zms.retrieveAccessDomain("coretech", principal); + assertEquals(zms.hasAccess(domain, "coretech", "read", "coretech:entity", principal, "trustdomain"), + AccessStatus.DENIED_INVALID_ROLE_TOKEN); + } + + @Test + public void testAccessNotFoundDomain() { + Principal principal = SimplePrincipal.create("user", "user1", "v=U1;d=user;n=user1;s=signature"); + try { + zms.access("read", "domain_not_found:entity", principal, null); + fail(); + } catch (com.yahoo.athenz.common.server.rest.ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testHasAccessValidMember() { + + TopLevelDomain dom1 = createTopLevelDomainObject("HasAccessDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("HasAccessDom1", "Role1", null, "user.user1", + "user.user3"); + zms.putRole(mockDomRsrcCtx, "HasAccessDom1", "Role1", auditRef, role1); + + Policy policy1 = createPolicyObject("HasAccessDom1", "Policy1", "Role1", + "UPDATE", "HasAccessDom1:resource1", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "HasAccessDom1", "Policy1", auditRef, policy1); + + // user1 and user3 have access to UPDATE/resource1 + + Principal principal1 = SimplePrincipal.create("user", "user1", "v=U1;d=user;n=user1;s=signature"); + AthenzDomain domain = zms.retrieveAccessDomain("hasaccessdom1", principal1); + + assertEquals(zms.hasAccess(domain, "hasaccessdom1", "update", "hasaccessdom1:resource1", + principal1, null), AccessStatus.ALLOWED); + + Principal principal3 = SimplePrincipal.create("user", "user3", "v=U1;d=user;n=user3;s=signature"); + assertEquals(zms.hasAccess(domain, "hasaccessdom1", "update", "hasaccessdom1:resource1", + principal3, null), AccessStatus.ALLOWED); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "HasAccessDom1", auditRef); + } + + @Test + public void testHasAccessInValidMember() { + + TopLevelDomain dom1 = createTopLevelDomainObject("HasAccessDom2", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject("HasAccessDom2", "Role1", null, "user.user1", + "user.user3"); + zms.putRole(mockDomRsrcCtx, "HasAccessDom2", "Role1", auditRef, role1); + + Policy policy1 = createPolicyObject("HasAccessDom2", "Policy1", "Role1", + "UPDATE", "HasAccessDom2:resource1", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, "HasAccessDom2", "Policy1", auditRef, policy1); + + // user2 does not have access to UPDATE/resource1 + + Principal principal2 = SimplePrincipal.create("user", "user2", "v=U1;d=user;n=user2;s=signature"); + + // this is internal zms function so the values passed have already been converted to lower + // case so we need to handle the test case accordingly. + + AthenzDomain domain = zms.retrieveAccessDomain("hasaccessdom2", principal2); + assertEquals(AccessStatus.DENIED, zms.hasAccess(domain, "hasaccessdom2", "update", + "hasaccessdom2:resource1", principal2, null)); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "HasAccessDom2", auditRef); + } + + @Test + public void testEvaluateAccessNoAssertions() { + + AthenzDomain domain = new AthenzDomain("coretech"); + Role role = new Role().setName("coretech:role.role1"); + domain.getRoles().add(role); + Policy policy = new Policy().setName("coretech:policy.policy1"); + domain.getPolicies().add(policy); + assertEquals(zms.evaluateAccess(domain, null, null, null, null, null), AccessStatus.DENIED); + } + + @Test + public void testEvaluateAccessAssertionDeny() { + + AthenzDomain domain = new AthenzDomain("coretech"); + Role role = createRoleObject("coretech", "role1", null, "user.user1", null); + domain.getRoles().add(role); + + Policy policy = new Policy().setName("coretech:policy.policy1"); + Assertion assertion = new Assertion(); + assertion.setAction("read"); + assertion.setEffect(AssertionEffect.DENY); + assertion.setResource("coretech:*"); + assertion.setRole("coretech:role.role1"); + policy.setAssertions(new ArrayList()); + policy.getAssertions().add(assertion); + domain.getPolicies().add(policy); + + assertEquals(zms.evaluateAccess(domain, "user.user1", "read", "coretech:resource1", + null, null), AccessStatus.DENIED); + } + + @Test + public void testEvaluateAccessAssertionAllow() { + + AthenzDomain domain = new AthenzDomain("coretech"); + Role role = createRoleObject("coretech", "role1", null, "user.user1", null); + domain.getRoles().add(role); + + Policy policy = new Policy().setName("coretech:policy.policy1"); + Assertion assertion = new Assertion(); + assertion.setAction("read"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("coretech:*"); + assertion.setRole("coretech:role.role1"); + policy.setAssertions(new ArrayList()); + policy.getAssertions().add(assertion); + domain.getPolicies().add(policy); + + assertEquals(zms.evaluateAccess(domain, "user.user1", "read", "coretech:resource1", null, null), AccessStatus.ALLOWED); + } + + @Test + public void testHasExceededDepthLimitNullLimit() { + assertFalse(zms.hasExceededDepthLimit(null, "domain")); + } + + @Test + public void testHasExceededDepthLimitNotValidLimit() { + assertTrue(zms.hasExceededDepthLimit(-1, "domain")); + assertTrue(zms.hasExceededDepthLimit(-1, "domain.sub1")); + } + + @Test + public void testHasExceededDepthLimitYes() { + assertTrue(zms.hasExceededDepthLimit(0, "domain.sub1")); + assertTrue(zms.hasExceededDepthLimit(1, "domain.sub1.sub2")); + assertTrue(zms.hasExceededDepthLimit(1, "domain.sub1.sub2.sub3")); + assertTrue(zms.hasExceededDepthLimit(2, "domain.sub1.sub2.sub3")); + } + + @Test + public void testHasExceededDepthLimitNo() { + assertFalse(zms.hasExceededDepthLimit(1, "domain.sub1")); + assertFalse(zms.hasExceededDepthLimit(2, "domain.sub1")); + assertFalse(zms.hasExceededDepthLimit(2, "domain.sub1.sub2")); + assertFalse(zms.hasExceededDepthLimit(3, "domain.sub1.sub2")); + assertFalse(zms.hasExceededDepthLimit(3, "domain.sub1.sub2.sub3")); + assertFalse(zms.hasExceededDepthLimit(4, "domain.sub1.sub2.sub3")); + } + + @Test + public void testIsZMSServiceYes() { + + assertTrue(zms.isZMSService("sys.auth", "zms")); + assertTrue(zms.isZMSService("sys.Auth", "ZMS")); + assertTrue(zms.isZMSService("SYS.AUTH", "ZMS")); + } + + @Test + public void testIsZMSServiceNo() { + + assertFalse(zms.isZMSService("sys.auth2", "zms")); + assertFalse(zms.isZMSService("sys.auth", "zts")); + } + + @Test + public void testRetrieveServiceIdentityInvalidServiceName() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceRetrieveDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServiceRetrieveDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceRetrieveDom1", "Service1", auditRef, service); + + try { + zms.getServiceIdentity(mockDomRsrcCtx, "ServiceRetrieveDom1", "Service"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + try { + zms.getServiceIdentity(mockDomRsrcCtx, "ServiceRetrieveDom1", "Service2"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + try { + zms.getServiceIdentity(mockDomRsrcCtx, "ServiceRetrieveDom1", "Service11"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceRetrieveDom1", auditRef); + } + + @Test + public void testRetriveServiceIdentityValid() { + + String domainName = "serviceretrievedom2"; + String serviceName = "service1"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject(domainName, + serviceName, "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, domainName, "Service1", auditRef, service); + + ServiceIdentity serviceRes = zms.getServiceIdentity(mockDomRsrcCtx, domainName, serviceName); + assertNotNull(serviceRes); + assertEquals(serviceRes.getName(), domainName + "." + serviceName); + assertEquals(serviceRes.getExecutable(), "/usr/bin/java"); + assertEquals(serviceRes.getGroup(), "users"); + assertEquals(serviceRes.getProviderEndpoint().toString(), "http://localhost"); + assertEquals(serviceRes.getUser(), "root"); + + List hosts = serviceRes.getHosts(); + assertNotNull(hosts); + assertEquals(hosts.size(), 1); + assertEquals(hosts.get(0), "host1"); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetProviderRoleActionPolicyNotFound() { + + String domainName = "coretech"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Assertion assertion = new Assertion(); + assertion.setAction("read"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("coretech:*"); + assertion.setRole("coretech:role.role1"); + + Policy policy = new Policy().setName("coretech:policy.provider"); + policy.setAssertions(new ArrayList()); + policy.getAssertions().add(assertion); + + zms.putPolicy(mockDomRsrcCtx, domainName, "provider", auditRef, policy); + + assertEquals(zms.getProviderRoleAction(domainName, "policy1"), ""); + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetProviderRoleActionAssertionNoMatch() { + + String domainName = "coretech"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Assertion assertion = new Assertion(); + assertion.setAction("read"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("coretech:*"); + assertion.setRole("coretech:role.role1"); + + Policy policy = new Policy().setName("coretech:policy.provider"); + policy.setAssertions(new ArrayList()); + policy.getAssertions().add(assertion); + + zms.putPolicy(mockDomRsrcCtx, domainName, "provider", auditRef, policy); + + assertEquals(zms.getProviderRoleAction(domainName, "provider"), ""); + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetProviderRoleActionAssertionActionNull() { + + String domainName = "coretech"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Assertion assertion = new Assertion(); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("coretech:*"); + assertion.setRole("coretech:role.provider"); + + Policy policy = new Policy().setName("coretech:policy.provider"); + policy.setAssertions(new ArrayList()); + policy.getAssertions().add(assertion); + + try { + zms.putPolicy(mockDomRsrcCtx, domainName, "provider", auditRef, policy); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + assertEquals(zms.getProviderRoleAction(domainName, "provider"), ""); + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetProviderRoleActionValid() { + + String domainName = "coretech"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Assertion assertion = new Assertion(); + assertion.setAction("read"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("coretech:*"); + assertion.setRole("coretech:role.provider"); + + Policy policy = new Policy().setName("coretech:policy.provider"); + policy.setAssertions(new ArrayList()); + policy.getAssertions().add(assertion); + + zms.putPolicy(mockDomRsrcCtx, domainName, "provider", auditRef, policy); + + assertEquals(zms.getProviderRoleAction(domainName, "provider"), "read"); + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testListDomains() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ListDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("ListDom2", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + DomainList domList = zms.listDomains(null, null, null, null, 0); + assertNotNull(domList); + + assertTrue(domList.getNames().contains("ListDom1".toLowerCase())); + assertTrue(domList.getNames().contains("ListDom2".toLowerCase())); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ListDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ListDom2", auditRef); + } + + @Test + public void testListDomainsParamsLimit() { + + TopLevelDomain dom1 = createTopLevelDomainObject("LimitDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("LimitDom2", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + DomainList domList = zms.listDomains(1, null, null, null, 0); + assertTrue(domList.getNames().size() == 1); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "LimitDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "LimitDom2", auditRef); + } + + @Test + public void testListDomainsParamsSkip() { + + TopLevelDomain dom1 = createTopLevelDomainObject("SkipDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("SkipDom2", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + TopLevelDomain dom3 = createTopLevelDomainObject("SkipDom3", + "Test Domain3", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom3); + + DomainList domList = zms.listDomains(null, null, null, null, 0); + int size = domList.getNames().size(); + assertTrue(size > 3); + + // ask for only for 2 domains back + domList = zms.listDomains(2, null, null, null, 0); + assertEquals(domList.getNames().size(), 2); + + // ask for the remaining domains + DomainList remList = zms.listDomains(null, domList.getNext(), null, null, 0); + assertEquals(remList.getNames().size(), size - 2); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "SkipDom1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "SkipDom2", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "SkipDom3", auditRef); + } + + @Test + public void testListDomainsParamsPrefix() { + + String noPrefixDom = "noprefixdom1"; + String prefixDom = "prefixdom2"; + + TopLevelDomain dom1 = createTopLevelDomainObject(noPrefixDom, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject(prefixDom, + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + DomainList domList = zms.listDomains(null, null, "prefix", null, 0); + + assertFalse(domList.getNames().contains(noPrefixDom)); + assertTrue(domList.getNames().contains(prefixDom)); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, noPrefixDom, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, prefixDom, auditRef); + } + + @Test + public void testListDomainsParamsDepth() { + + TopLevelDomain dom1 = createTopLevelDomainObject("DepthDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + SubDomain dom2 = createSubDomainObject("DepthDom2", "DepthDom1", + "Test Domain2", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "DepthDom1", auditRef, dom2); + + SubDomain dom3 = createSubDomainObject("DepthDom3", + "DepthDom1.DepthDom2", "Test Domain3", "testOrg", adminUser); + zms.postSubDomain(mockDomRsrcCtx, "DepthDom1.DepthDom2", auditRef, dom3); + + DomainList domList = zms.listDomains(null, null, null, 1, 0); + + assertTrue(domList.getNames().contains("DepthDom1".toLowerCase())); + assertTrue(domList.getNames().contains("DepthDom1.DepthDom2".toLowerCase())); + assertFalse(domList.getNames().contains("DepthDom1.DepthDom2.DepthDom3".toLowerCase())); + + zms.deleteSubDomain(mockDomRsrcCtx, "DepthDom1.DepthDom2", "DepthDom3", auditRef); + zms.deleteSubDomain(mockDomRsrcCtx, "DepthDom1", "DepthDom2", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "DepthDom1", auditRef); + } + + @Test + public void testListModifiedDomains() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ListDomMod1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("ListDomMod2", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + DomainModifiedList domModList = zms.dbService.listModifiedDomains(0); + assertNotNull(domModList); + assertTrue(domModList.getNameModList().size() > 1); + + boolean dom1Found = false; + boolean dom2Found = false; + for (DomainModified domName : domModList.getNameModList()) { + if (domName.getName().equalsIgnoreCase("ListDomMod1")) { + dom1Found = true; + } else if (domName.getName().equalsIgnoreCase("ListDomMod2")) { + dom2Found = true; + } + } + + assertTrue(dom1Found); + assertTrue(dom2Found); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ListDomMod1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ListDomMod2", auditRef); + } + + @Test + public void testListModifiedDomainsMillis() { + + long timestamp = System.currentTimeMillis() - 1001; + + TopLevelDomain dom1 = createTopLevelDomainObject("ListDomMod1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject("ListDomMod2", + "Test Domain2", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + + DomainModifiedList domModList = zms.dbService.listModifiedDomains(timestamp); + assertNotNull(domModList); + assertTrue(domModList.getNameModList().size() > 1); + + boolean dom1Found = false; + boolean dom2Found = false; + for (DomainModified domName : domModList.getNameModList()) { + if (domName.getName().equalsIgnoreCase("ListDomMod1")) { + dom1Found = true; + } else if (domName.getName().equalsIgnoreCase("ListDomMod2")) { + dom2Found = true; + } + } + + assertTrue(dom1Found); + assertTrue(dom2Found); + + timestamp += 10000; // add 10 seconds + domModList = zms.dbService.listModifiedDomains(timestamp); + assertNotNull(domModList); + assertTrue(domModList.getNameModList().size() == 0); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ListDomMod1", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ListDomMod2", auditRef); + } + + @Test + public void testVirtualHomeDomain() { + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + + Principal principal = SimplePrincipal.create("user", "user1", "v=U1;d=user;n=user1;s=signature", + 0, principalAuthority); + + AthenzDomain virtualDomain = zms.virtualHomeDomain(principal); + assertNotNull(virtualDomain); + + List roles = virtualDomain.getRoles(); + assertNotNull(roles); + Role adminRole = null; + for (Role role : roles) { + if (role.getName().equals("user.user1:role.admin")) { + adminRole = role; + break; + } + } + assertNotNull(adminRole); + + List policies = virtualDomain.getPolicies(); + assertNotNull(policies); + Policy adminPolicy = null; + for (Policy policy : policies) { + if (policy.getName().equals("user.user1:policy.admin")) { + adminPolicy = policy; + break; + } + } + assertNotNull(adminPolicy); + } + + @Test + public void testDeletePublicKeyEntry() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceDelPubKeyDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServiceDelPubKeyDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceDelPubKeyDom1", "Service1", auditRef, service); + + zms.deletePublicKeyEntry(mockDomRsrcCtx, "ServiceDelPubKeyDom1", "Service1", "1", auditRef); + ServiceIdentity serviceRes = zms.getServiceIdentity(mockDomRsrcCtx, "ServiceDelPubKeyDom1", "Service1"); + List keyList = serviceRes.getPublicKeys(); + boolean found = false; + for (PublicKeyEntry entry : keyList) { + if (entry.getId().equals("1")) { + found = true; + } + } + assertFalse(found); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceDelPubKeyDom1", auditRef); + } + + @Test + public void testDeletePublicKeyEntryMissingAuditRef() { + String domain = "testDeletePublicKeyEntryMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + ServiceIdentity service = createServiceObject( + domain, + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + zms.putServiceIdentity(mockDomRsrcCtx, domain, "Service1", auditRef, service); + + PublicKeyEntry keyEntry = new PublicKeyEntry(); + keyEntry.setId("zone1"); + keyEntry.setKey(pubKeyK2); + zms.putPublicKeyEntry(mockDomRsrcCtx, domain, "Service1", "zone1", auditRef, keyEntry); + try { + zms.deletePublicKeyEntry(mockDomRsrcCtx, domain, "Service1", "1", null); + fail("requesterror not thrown by deletePublicKeyEntry."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testDeletePublicKeyEntryDomainNotFound() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceDelPubKeyDom2", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServiceDelPubKeyDom2", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceDelPubKeyDom2", "Service1", auditRef, service); + + // this should throw a not found exception + try { + zms.deletePublicKeyEntry(mockDomRsrcCtx, "UnknownPublicKeyDomain", "Service1", "1", auditRef); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceDelPubKeyDom2", auditRef); + } + + @Test + public void testDeletePublicKeyEntryInvalidService() { + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_delpubkeyinvalidsvc"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceDelPubKeyDom2InvalidService", + "Test Domain1", "testOrg", adminUser); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServiceDelPubKeyDom2InvalidService", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zmsImpl.putServiceIdentity(mockDomRsrcCtx, "ServiceDelPubKeyDom2InvalidService", "Service1", auditRef, service); + + // this should throw an invalid request exception + try { + zmsImpl.deletePublicKeyEntry(mockDomRsrcCtx, "ServiceDelPubKeyDom2InvalidService", "Service1.Service2", "1", auditRef); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + String caller = "deletepublickeyentry"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(Invalid SimpleName error: String pattern mismatch (expected \"[a-zA-Z0-9_][a-zA-Z0-9_-]*\") for type SimpleName in data);:caller specified keyId=(1)") != -1); + foundError = true; + break; + } + assertTrue(foundError); + + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceDelPubKeyDom2InvalidService", auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + + @Test + public void testDeletePublicKeyEntryServiceNotFound() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceDelPubKeyDom3", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServiceDelPubKeyDom3", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceDelPubKeyDom3", "Service1", auditRef, service); + + // this should throw a not found exception + try { + zms.deletePublicKeyEntry(mockDomRsrcCtx, "ServiceDelPubKeyDom3", "ServiceNotFound", "1", auditRef); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceDelPubKeyDom3", auditRef); + } + + @Test + public void testDeletePublicKeyEntryIdNotFound() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServiceDelPubKeyDom4", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServiceDelPubKeyDom4", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServiceDelPubKeyDom4", "Service1", auditRef, service); + + // process invalid keys + + try { + zms.deletePublicKeyEntry(mockDomRsrcCtx, "ServiceDelPubKeyDom4", "Service1", "zone", auditRef); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + // make sure both 1 and 2 keys are still valid + + ServiceIdentity serviceRes = zms.getServiceIdentity(mockDomRsrcCtx, "ServiceDelPubKeyDom4", "Service1"); + List keyList = serviceRes.getPublicKeys(); + boolean foundKey1 = false; + boolean foundKey2 = false; + for (PublicKeyEntry entry : keyList) { + if (entry.getId().equals("1")) { + foundKey1 = true; + } else if (entry.getId().equals("2")) { + foundKey2 = true; + } + } + assertTrue(foundKey1); + assertTrue(foundKey2); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServiceDelPubKeyDom4", auditRef); + } + + @Test + public void testGetPublicKeyEntry() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServicePubKeyDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServicePubKeyDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServicePubKeyDom1", "Service1", auditRef, service); + + PublicKeyEntry entry = zms.getPublicKeyEntry(mockDomRsrcCtx, "ServicePubKeyDom1", "Service1", "1"); + assertNotNull(entry); + assertEquals(entry.getId(), "1"); + assertEquals(entry.getKey(), pubKeyK1); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServicePubKeyDom1", auditRef); + } + + @Test + public void testGetPublicKeyEntryInvalidService() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServicePubKeyDom2Invalid", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServicePubKeyDom2Invalid", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServicePubKeyDom2Invalid", "Service1", auditRef, service); + + // this should throw an invalid request exception + try { + zms.getPublicKeyEntry(mockDomRsrcCtx, "ServicePubKeyDom2Invalid", "Service1.Service2", "1"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServicePubKeyDom2Invalid", auditRef); + } + + @Test + public void testGetPublicKeyEntryDomainNotFound() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServicePubKeyDom2", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServicePubKeyDom2", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServicePubKeyDom2", "Service1", auditRef, service); + + // this should throw a not found exception + try { + zms.getPublicKeyEntry(mockDomRsrcCtx, "UnknownPublicKeyDomain", "Service1", "1"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServicePubKeyDom2", auditRef); + } + + @Test + public void testGetPublicKeyEntryServiceNotFound() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServicePubKeyDom3", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServicePubKeyDom3", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServicePubKeyDom3", "Service1", auditRef, service); + + // this should throw a not found exception + try { + zms.getPublicKeyEntry(mockDomRsrcCtx, "ServicePubKeyDom3", "ServiceNotFound", "1"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServicePubKeyDom3", auditRef); + } + + @Test + public void testGetPublicKeyEntryIdNotFound() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServicePubKeyDom4", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServicePubKeyDom4", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServicePubKeyDom4", "Service1", auditRef, service); + + // this should throw a not found exception + try { + zms.getPublicKeyEntry(mockDomRsrcCtx, "ServicePubKeyDom4", "Service1", "zone"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServicePubKeyDom4", auditRef); + } + + @Test + public void testPutPublicKeyEntryNew() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServicePutPubKeyDom1", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServicePutPubKeyDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServicePutPubKeyDom1", "Service1", auditRef, service); + + PublicKeyEntry keyEntry = new PublicKeyEntry(); + keyEntry.setId("zone1"); + keyEntry.setKey(pubKeyK2); + + zms.putPublicKeyEntry(mockDomRsrcCtx, "ServicePutPubKeyDom1", "Service1", "zone1", auditRef, keyEntry); + + ServiceIdentity serviceRes = zms.getServiceIdentity(mockDomRsrcCtx, "ServicePutPubKeyDom1", "Service1"); + List keyList = serviceRes.getPublicKeys(); + boolean foundKey1 = false; + boolean foundKey2 = false; + boolean foundKeyZONE1 = false; + for (PublicKeyEntry entry : keyList) { + if (entry.getId().equals("1")) { + foundKey1 = true; + } else if (entry.getId().equals("2")) { + foundKey2 = true; + } else if (entry.getId().equals("zone1")) { + foundKeyZONE1 = true; + } + } + assertTrue(foundKey1); + assertTrue(foundKey2); + assertTrue(foundKeyZONE1); + + PublicKeyEntry entry = zms.getPublicKeyEntry(mockDomRsrcCtx, "ServicePutPubKeyDom1", "Service1", "zone1"); + assertNotNull(entry); + assertEquals(entry.getId(), "zone1"); + assertEquals(entry.getKey(), pubKeyK2); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServicePutPubKeyDom1", auditRef); + } + + @Test + public void testPutPublicKeyEntryMissingAuditRef() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_putpubkeyentrymissauditref"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String domain = "testPutPublicKeyEntryMissingAuditRef"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + dom.setAuditEnabled(true); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + ServiceIdentity service = createServiceObject( + domain, + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + zmsImpl.putServiceIdentity(mockDomRsrcCtx, domain, "Service1", auditRef, service); + + PublicKeyEntry keyEntry = new PublicKeyEntry(); + keyEntry.setId("zone1"); + keyEntry.setKey(pubKeyK2); + + try { + zmsImpl.putPublicKeyEntry(mockDomRsrcCtx, domain, "Service1", "zone1", null, keyEntry); + fail("requesterror not thrown by putPublicKeyEntry."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + assertTrue(ex.getMessage().contains("Audit reference required")); + + String caller = "putpublickeyentry"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(putpublickeyentry: Audit reference required for domain: testputpublickeyentrymissingauditref)") != -1); + foundError = true; + break; + } + assertTrue(foundError); + } finally { + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + } + + @Test + public void testPutPublicKeyEntryInvalidService() { + + String domain = "testPutPublicKeyEntryInvalidService"; + TopLevelDomain dom = createTopLevelDomainObject( + domain, "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + ServiceIdentity service = createServiceObject( + domain, + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + zms.putServiceIdentity(mockDomRsrcCtx, domain, "Service1", auditRef, service); + + PublicKeyEntry keyEntry = new PublicKeyEntry(); + keyEntry.setId("zone1"); + keyEntry.setKey(pubKeyK2); + + try { + zms.putPublicKeyEntry(mockDomRsrcCtx, domain, "Service1.Service2", "zone1", null, keyEntry); + fail("requesterror not thrown by putPublicKeyEntry."); + } catch (ResourceException ex) { + assertTrue(ex.getCode() == 400); + } finally { + zms.deleteTopLevelDomain(mockDomRsrcCtx, domain, auditRef); + } + } + + @Test + public void testPutPublicKeyEntryUpdate() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServicePutPubKeyDom1A", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServicePutPubKeyDom1A", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServicePutPubKeyDom1A", "Service1", auditRef, service); + + PublicKeyEntry keyEntry = new PublicKeyEntry(); + keyEntry.setId("1"); + keyEntry.setKey(pubKeyK2); + + zms.putPublicKeyEntry(mockDomRsrcCtx, "ServicePutPubKeyDom1A", "Service1", "1", auditRef, keyEntry); + + ServiceIdentity serviceRes = zms.getServiceIdentity(mockDomRsrcCtx, "ServicePutPubKeyDom1A", "Service1"); + List keyList = serviceRes.getPublicKeys(); + assertEquals(keyList.size(), 2); + + boolean foundKey1 = false; + boolean foundKey2 = false; + for (PublicKeyEntry entry : keyList) { + if (entry.getId().equals("1")) { + foundKey1 = true; + } else if (entry.getId().equals("2")) { + foundKey2 = true; + } + } + + assertTrue(foundKey1); + assertTrue(foundKey2); + + PublicKeyEntry entry = zms.getPublicKeyEntry(mockDomRsrcCtx, "ServicePutPubKeyDom1A", "Service1", "1"); + assertNotNull(entry); + assertEquals(entry.getId(), "1"); + assertEquals(entry.getKey(), pubKeyK2); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServicePutPubKeyDom1A", auditRef); + } + + @Test + public void testPutPublicKeyEntryDomainNotFound() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServicePutPubKeyDom2", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServicePutPubKeyDom2", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServicePutPubKeyDom2", "Service1", auditRef, service); + + // this should throw a not found exception + try { + PublicKeyEntry keyEntry = new PublicKeyEntry(); + keyEntry.setId("zone1"); + keyEntry.setKey(pubKeyK2); + + zms.putPublicKeyEntry(mockDomRsrcCtx, "UnknownPublicKeyDomain", "Service1", "zone1", auditRef, keyEntry); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServicePutPubKeyDom2", auditRef); + } + + @Test + public void testPutPublicKeyEntryServiceNotFound() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServicePutPubKeyDom3", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServicePutPubKeyDom3", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServicePutPubKeyDom3", "Service1", auditRef, service); + + // this should throw a not found exception + try { + PublicKeyEntry keyEntry = new PublicKeyEntry(); + keyEntry.setId("zone1"); + keyEntry.setKey(pubKeyK2); + + zms.putPublicKeyEntry(mockDomRsrcCtx, "ServicePutPubKeyDom3", "ServiceNotFound", "zone1", auditRef, keyEntry); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServicePutPubKeyDom3", auditRef); + } + + @Test + public void testDeletePublicKeyEntryIdNoMatch() { + + TopLevelDomain dom1 = createTopLevelDomainObject("ServicePutPubKeyDom4", + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service = createServiceObject("ServicePutPubKeyDom4", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + zms.putServiceIdentity(mockDomRsrcCtx, "ServicePutPubKeyDom4", "Service1", auditRef, service); + + // this should throw invalid request exception + + try { + PublicKeyEntry keyEntry = new PublicKeyEntry(); + keyEntry.setId("zone1"); + keyEntry.setKey(pubKeyK2); + + zms.putPublicKeyEntry(mockDomRsrcCtx, "ServicePutPubKeyDom4", "Service1", "zone2", auditRef, keyEntry); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, "ServicePutPubKeyDom4", auditRef); + } + + @Test + public void testSleepBeforeRetryingRequestExpireZero() { + + long timeStamp = System.currentTimeMillis(); + zms.sleepBeforeRetryingRequest(0, 2000, "testSleep"); + assertTrue(System.currentTimeMillis() - timeStamp < 1000); + } + + @Test + public void testSleepBeforeRetryingRequestExpireNegative() { + + long timeStamp = System.currentTimeMillis(); + zms.sleepBeforeRetryingRequest(-1, 2000, "testSleep"); + assertTrue(System.currentTimeMillis() - timeStamp < 1000); + } + + @Test + public void testSleepBeforeRetryingRequest() { + + long timeStamp = System.currentTimeMillis(); + zms.sleepBeforeRetryingRequest(50, 2000, "testSleep"); + + // let's assume we'll never get interrupted + assertTrue(System.currentTimeMillis() - timeStamp >= 2000); + } + + @Test + public void testConverToLowerCaseAssertion() { + + Assertion assertion = new Assertion(); + assertion.setAction("Read"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("coreTech:VIP.*"); + assertion.setRole("coretech:role.Role1"); + + AthenzObject.ASSERTION.convertToLowerCase(assertion); + assertEquals(assertion.getRole(), "coretech:role.role1"); + assertEquals(assertion.getAction(), "read"); + assertEquals(assertion.getResource(), "coretech:vip.*"); + } + + @Test + public void testRemoveQuotes() { + + assertEquals(zms.removeQuotes("abc"), "abc"); + assertEquals(zms.removeQuotes("\"abc"), "abc"); + assertEquals(zms.removeQuotes("abc\""), "abc"); + assertEquals(zms.removeQuotes("\"abc\""), "abc"); + assertEquals(zms.removeQuotes("\"a\"bc\""), "a\"bc"); + } + + @Test + public void testConvertToLowerCaseList() { + + AthenzObject.LIST.convertToLowerCase(null); + + List list = new ArrayList<>(); + list.add("item1"); + list.add("Item2"); + list.add("ITEM3"); + + AthenzObject.LIST.convertToLowerCase(list); + assertTrue(list.contains("item1")); + assertTrue(list.contains("item2")); + assertTrue(list.contains("item3")); + assertEquals(list.size(), 3); + } + + @Test + public void testConvertToLowerCaseSubdomain() { + + SubDomain dom = createSubDomainObject("DepthDom2", "DepthDom1", + "Test Domain2", "testOrg", "user.user3A"); + AthenzObject.SUB_DOMAIN.convertToLowerCase(dom); + assertEquals(dom.getName(), "depthdom2"); + assertEquals(dom.getParent(), "depthdom1"); + assertTrue(dom.getAdminUsers().contains("user.user3a")); + + SubDomain dom2 = createSubDomainObject("DepthDom2", "DepthDom1", + "Test Domain2", "testOrg", "user.user3B"); + DomainTemplateList templates = new DomainTemplateList(); + List list = new ArrayList<>(); + list.add("platforms"); + list.add("vipNg"); + list.add("ATHENZ"); + templates.setTemplateNames(list); + dom2.setTemplates(templates); + AthenzObject.SUB_DOMAIN.convertToLowerCase(dom2); + assertEquals(dom2.getName(), "depthdom2"); + assertEquals(dom2.getParent(), "depthdom1"); + assertTrue(dom2.getAdminUsers().contains("user.user3b")); + templates = dom2.getTemplates(); + list = templates.getTemplateNames(); + assertEquals(3, list.size()); + assertTrue(list.contains("platforms")); + assertTrue(list.contains("vipng")); + assertTrue(list.contains("athenz")); + } + + @Test + public void testConvertToLowerCaseTopLeveldomain() { + + TopLevelDomain dom = createTopLevelDomainObject("TopLevelDomain", + "Test Domain1", "testOrg", "user.USER3A"); + AthenzObject.TOP_LEVEL_DOMAIN.convertToLowerCase(dom); + assertEquals(dom.getName(), "topleveldomain"); + assertTrue(dom.getAdminUsers().contains("user.user3a")); + + TopLevelDomain dom2 = createTopLevelDomainObject("TopLevelDomain", + "Test Domain1", "testOrg", "user.USER3B"); + DomainTemplateList templates = new DomainTemplateList(); + List list = new ArrayList<>(); + list.add("platforms"); + list.add("vipNg"); + list.add("ATHENZ"); + templates.setTemplateNames(list); + dom2.setTemplates(templates); + AthenzObject.TOP_LEVEL_DOMAIN.convertToLowerCase(dom2); + assertEquals(dom2.getName(), "topleveldomain"); + assertTrue(dom2.getAdminUsers().contains("user.user3b")); + templates = dom2.getTemplates(); + list = templates.getTemplateNames(); + assertEquals(3, list.size()); + assertTrue(list.contains("platforms")); + assertTrue(list.contains("vipng")); + assertTrue(list.contains("athenz")); + } + + @Test + public void testConvertToLowerCaseUserdomain() { + + UserDomain dom = createUserDomainObject("USER3A", + "Test Domain1", "testOrg"); + AthenzObject.USER_DOMAIN.convertToLowerCase(dom); + assertEquals(dom.getName(), "user3a"); + + UserDomain dom2 = createUserDomainObject("USER3B", + "Test Domain1", "testOrg"); + DomainTemplateList templates = new DomainTemplateList(); + List list = new ArrayList<>(); + list.add("platforms"); + list.add("vipNg"); + list.add("ATHENZ"); + templates.setTemplateNames(list); + dom2.setTemplates(templates); + + AthenzObject.USER_DOMAIN.convertToLowerCase(dom2); + assertEquals(dom2.getName(), "user3b"); + templates = dom2.getTemplates(); + list = templates.getTemplateNames(); + assertEquals(3, list.size()); + assertTrue(list.contains("platforms")); + assertTrue(list.contains("vipng")); + assertTrue(list.contains("athenz")); + } + + @Test + public void testConvertToLowerCasePublicKeyEntry() { + PublicKeyEntry keyEntry = new PublicKeyEntry().setKey("KEY").setId("ZONE1"); + AthenzObject.PUBLIC_KEY_ENTRY.convertToLowerCase(keyEntry); + assertEquals(keyEntry.getKey(), "KEY"); + assertEquals(keyEntry.getId(), "zone1"); + } + + @Test + public void testConvertToLowerCaseEntity() { + Entity entity = createEntityObject("ABcEntity"); + AthenzObject.ENTITY.convertToLowerCase(entity); + assertEquals(entity.getName(), "abcentity"); + } + + @Test + public void testConvertToLowerCaseTenancy() { + Tenancy tenancy = createTenantObject("CoretecH", "STorage"); + List groups = new ArrayList(); + groups.add("Burbank"); + groups.add("santa_monica"); + tenancy.setResourceGroups(groups); + AthenzObject.TENANCY.convertToLowerCase(tenancy); + assertEquals(tenancy.getDomain(), "coretech"); + assertEquals(tenancy.getService(), "storage"); + assertTrue(tenancy.getResourceGroups().contains("burbank")); + assertTrue(tenancy.getResourceGroups().contains("santa_monica")); + } + + @Test + public void testConvertToLowerCaseTenancyResourceGroup() { + TenancyResourceGroup tenancyResourceGroup = new TenancyResourceGroup(); + tenancyResourceGroup.setDomain("CoretecH").setService("STorage").setResourceGroup("Group1"); + AthenzObject.TENANCY_RESOURCE_GROUP.convertToLowerCase(tenancyResourceGroup); + assertEquals("coretech", tenancyResourceGroup.getDomain()); + assertEquals("storage", tenancyResourceGroup.getService()); + assertEquals("group1", tenancyResourceGroup.getResourceGroup()); + } + + @Test + public void testConvertToLowerCaseDefaultAdmins() { + + List adminList = new ArrayList(); + adminList.add("user.User1"); + adminList.add("user.user2"); + DefaultAdmins admins = new DefaultAdmins(); + admins.setAdmins(adminList); + + AthenzObject.DEFAULT_ADMINS.convertToLowerCase(admins); + assertTrue(admins.getAdmins().contains("user.user1")); + assertTrue(admins.getAdmins().contains("user.user2")); + } + + @Test + public void testConvertToLowerCaseTenantRolesNoActions() { + + TenantRoles tenantRoles = new TenantRoles().setDomain("coreTech") + .setService("storaGe").setTenant("DelTenantRolesDom1"); + AthenzObject.TENANT_ROLES.convertToLowerCase(tenantRoles); + assertEquals(tenantRoles.getDomain(), "coretech"); + assertEquals(tenantRoles.getService(), "storage"); + assertEquals(tenantRoles.getTenant(), "deltenantrolesdom1"); + } + + @Test + public void testConvertToLowerCaseTenantResourceGroupRolesNoActions() { + + TenantResourceGroupRoles tenantRoles = new TenantResourceGroupRoles() + .setDomain("coreTech").setService("storaGe") + .setTenant("DelTenantRolesDom1").setResourceGroup("Hockey"); + AthenzObject.TENANT_RESOURCE_GROUP_ROLES.convertToLowerCase(tenantRoles); + assertEquals(tenantRoles.getDomain(), "coretech"); + assertEquals(tenantRoles.getService(), "storage"); + assertEquals(tenantRoles.getTenant(), "deltenantrolesdom1"); + assertEquals(tenantRoles.getResourceGroup(), "hockey"); + } + + @Test + public void testConvertToLowerCaseProviderResourceGroupRolesNoActions() { + + ProviderResourceGroupRoles tenantRoles = new ProviderResourceGroupRoles() + .setDomain("coreTech").setService("storaGe") + .setTenant("DelTenantRolesDom1").setResourceGroup("Hockey"); + AthenzObject.PROVIDER_RESOURCE_GROUP_ROLES.convertToLowerCase(tenantRoles); + assertEquals(tenantRoles.getDomain(), "coretech"); + assertEquals(tenantRoles.getService(), "storage"); + assertEquals(tenantRoles.getTenant(), "deltenantrolesdom1"); + assertEquals(tenantRoles.getResourceGroup(), "hockey"); + } + + @Test + public void testConvertToLowerCaseGroupRole() { + Role role = createRoleObject("RoleDomain", "roleName", null, "user.USER1", "user.user2"); + AthenzObject.ROLE.convertToLowerCase(role); + assertEquals(role.getName(), "roledomain:role.rolename"); + assertTrue(role.getMembers().contains("user.user1")); + assertTrue(role.getMembers().contains("user.user2")); + } + + @Test + public void testConvertToLowerCaseTrustRole() { + Role role = createRoleObject("RoleDomain", "roleName", "TRUSTDomain"); + AthenzObject.ROLE.convertToLowerCase(role); + assertEquals(role.getName(), "roledomain:role.rolename"); + assertEquals(role.getTrust(), "trustdomain"); + } + + @Test + public void testConvertToLowerCaseMembershipWithRole() { + Membership membership = new Membership().setMemberName("user.member1").setRoleName("ROLE1"); + AthenzObject.MEMBERSHIP.convertToLowerCase(membership); + assertEquals(membership.getMemberName(), "user.member1"); + assertEquals(membership.getRoleName(), "role1"); + } + + @Test + public void testConvertToLowerCaseMembershipWithoutRole() { + Membership membership = new Membership().setMemberName("user.member1"); + AthenzObject.MEMBERSHIP.convertToLowerCase(membership); + assertEquals(membership.getMemberName(), "user.member1"); + } + + @Test + public void testConvertToLowerCaseServciceWithKeys() { + ServiceIdentity service = createServiceObject("CoreTECH", "STORage", + "http://localhost:4080", "jetty", "user", "group", "HOST1"); + List publicKeyList = new ArrayList(); + PublicKeyEntry publicKeyEntry1 = new PublicKeyEntry(); + publicKeyEntry1.setKey(pubKeyK1); + publicKeyEntry1.setId("ZONE1"); + publicKeyList.add(publicKeyEntry1); + PublicKeyEntry publicKeyEntry2 = new PublicKeyEntry(); + publicKeyEntry2.setKey(pubKeyK2); + publicKeyEntry2.setId("2"); + publicKeyList.add(publicKeyEntry2); + service.setPublicKeys(publicKeyList); + AthenzObject.SERVICE_IDENTITY.convertToLowerCase(service); + assertEquals(service.getName(), "coretech.storage"); + assertTrue(service.getHosts().contains("host1")); + assertEquals(service.getPublicKeys().get(0).getId(), "zone1"); + assertEquals(service.getPublicKeys().get(1).getId(), "2"); + } + + @Test + public void testConvertToLowerCaseTenantRolesWithActions() { + + List roleActions = new ArrayList(); + roleActions.add(new TenantRoleAction().setRole("Role").setAction("WRITE")); + + TenantRoles tenantRoles = new TenantRoles().setDomain("CORETECH") + .setService("storage").setTenant("DelTenantRolesDom1") + .setRoles(roleActions); + + AthenzObject.TENANT_ROLES.convertToLowerCase(tenantRoles); + assertEquals(tenantRoles.getDomain(), "coretech"); + assertEquals(tenantRoles.getService(), "storage"); + assertEquals(tenantRoles.getTenant(), "deltenantrolesdom1"); + TenantRoleAction roleAction = tenantRoles.getRoles().get(0); + assertEquals(roleAction.getAction(), "write"); + assertEquals(roleAction.getRole(), "role"); + } + + @Test + public void testConvertToLowerCaseTenantRoleAction() { + + TenantRoleAction roleAction = new TenantRoleAction().setRole("ReaDer").setAction("READ"); + + AthenzObject.TENANT_ROLE_ACTION.convertToLowerCase(roleAction); + assertEquals(roleAction.getAction(), "read"); + assertEquals(roleAction.getRole(), "reader"); + } + + @Test + public void testConvertToLowerCasePolicyNoAssertion() { + + Policy policy = new Policy(); + policy.setName(ZMSUtils.policyResourceName("CoreTech", "policy")); + + AthenzObject.POLICY.convertToLowerCase(policy); + assertEquals(policy.getName(), "coretech:policy.policy"); + + policy.setName(ZMSUtils.policyResourceName("newtech", "Policy")); + + AthenzObject.POLICY.convertToLowerCase(policy); + assertEquals(policy.getName(), "newtech:policy.policy"); + } + + @Test + public void testConvertToLowerCasePolicyMultipleAssertion() { + + Policy policy = new Policy(); + policy.setName(ZMSUtils.policyResourceName("CoreTech", "policy")); + + Assertion assertion1 = new Assertion(); + assertion1.setAction("Read"); + assertion1.setEffect(AssertionEffect.ALLOW); + assertion1.setResource("coreTech:VIP.*"); + assertion1.setRole("coretech:role.Role1"); + + Assertion assertion2 = new Assertion(); + assertion2.setAction("UPDATE"); + assertion2.setEffect(AssertionEffect.ALLOW); + assertion2.setResource("CoreTech:VIP.*"); + assertion2.setRole("coretech:role.RoleAB"); + + List assertList = new ArrayList(); + assertList.add(assertion1); + assertList.add(assertion2); + + policy.setAssertions(assertList); + + AthenzObject.POLICY.convertToLowerCase(policy); + assertEquals(policy.getName(), "coretech:policy.policy"); + Assertion assertion = policy.getAssertions().get(0); + assertEquals(assertion.getRole(), "coretech:role.role1"); + assertEquals(assertion.getAction(), "read"); + assertEquals(assertion.getResource(), "coretech:vip.*"); + + assertion = policy.getAssertions().get(1); + assertEquals(assertion.getRole(), "coretech:role.roleab"); + assertEquals(assertion.getAction(), "update"); + assertEquals(assertion.getResource(), "coretech:vip.*"); + } + + @Test + public void testConvertToLowerCasePolicyOneAssertion() { + + Policy policy = createPolicyObject("CoreTech", "NewPolicy"); + AthenzObject.POLICY.convertToLowerCase(policy); + assertEquals(policy.getName(), "coretech:policy.newpolicy"); + Assertion assertion = policy.getAssertions().get(0); + assertEquals(assertion.getRole(), "coretech:role.role1"); + } + + @Test + public void testConvertToLowerCaseDomainTemplateList() { + DomainTemplateList templates = new DomainTemplateList(); + List list = new ArrayList<>(); + list.add("platforms"); + list.add("vipNg"); + list.add("ATHENZ"); + templates.setTemplateNames(list); + AthenzObject.DOMAIN_TEMPLATE_LIST.convertToLowerCase(templates); + + list = templates.getTemplateNames(); + assertEquals(3, list.size()); + assertTrue(list.contains("platforms")); + assertTrue(list.contains("vipng")); + assertTrue(list.contains("athenz")); + } + + @Test + public void testProviderServiceDomain() { + assertEquals(zms.providerServiceDomain("coretech.storage"), "coretech"); + assertEquals(zms.providerServiceDomain("coretech.hosted.storage"), "coretech.hosted"); + assertNull(zms.providerServiceDomain("coretech")); + assertNull(zms.providerServiceDomain(".coretech")); + assertNull(zms.providerServiceDomain("coretech.")); + } + + @Test + public void testProviderServiceName() { + assertEquals(zms.providerServiceName("coretech.storage"), "storage"); + assertEquals(zms.providerServiceName("coretech.hosted.storage"), "storage"); + assertNull(zms.providerServiceName("coretech")); + assertNull(zms.providerServiceName(".coretech")); + assertNull(zms.providerServiceName("coretech.")); + } + + @Test + public void testIsAuthorizedProviderServiceInvalidService() { + + // null authorized service argument + + assertFalse(zms.isAuthorizedProviderService(null, "coretech", "storage", "sports", auditRef)); + + // service does not match provider details + + assertFalse(zms.isAuthorizedProviderService("coretech.storage", "coretech", "storage2", "sports", auditRef)); + assertFalse(zms.isAuthorizedProviderService("coretech.storage", "coretech2", "storage", "sports", auditRef)); + + // domain does not exist in zms + + assertFalse(zms.isAuthorizedProviderService("not_present_domain.storage", "not_present_domain", + "storage", "sports", auditRef)); + } + + @Test + public void testIsAuthorizedProviderServiceAuthorized() { + + String tenantDomain = "AuthorizedProviderDom1"; + String providerDomain = "coretech"; + setupTenantDomainProviderService(tenantDomain, providerDomain, "storage", + "http://localhost:8090/tableprovider"); + + // tenant is setup so let's setup up policy to authorize access to tenants + + Role role = createRoleObject(providerDomain, "self_serve", null, providerDomain + ".storage", null); + zms.putRole(mockDomRsrcCtx, providerDomain, "self_serve", auditRef, role); + + Policy policy = createPolicyObject(providerDomain, "self_serve", + "self_serve", "update", providerDomain + ":tenant.*", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, providerDomain, "self_serve", auditRef, policy); + + assertTrue(zms.isAuthorizedProviderService(providerDomain + ".storage", providerDomain, + "storage", tenantDomain, auditRef)); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + } + + @Test + public void testIsAuthorizedProviderServiceNotAuthorized() { + + String tenantDomain = "AuthorizedProviderDom2"; + String providerDomain = "coretech"; + setupTenantDomainProviderService(tenantDomain, providerDomain, "storage", + "http://localhost:8090/tableprovider"); + + // tenant is setup but no policy to authorize access to tenants + + assertFalse(zms.isAuthorizedProviderService(providerDomain + ".storage", providerDomain, + "storage", tenantDomain, auditRef)); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + } + + @Test + public void testVerifyAuthorizedServiceOperation() { + + // null authorized service means it's all good + + zms.verifyAuthorizedServiceOperation(null, "putrole"); + + // our test resource json file includes two services: + // coretech.storage - allowed for putrole and putpolicy + // sports.hockey - allowed for all ops + + zms.verifyAuthorizedServiceOperation("coretech.storage", "putrole"); + zms.verifyAuthorizedServiceOperation("coretech.storage", "putpolicy"); + try { + zms.verifyAuthorizedServiceOperation("coretech.storage", "postdomain"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + } + try { + zms.verifyAuthorizedServiceOperation("coretech.storage", "deleterole"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + } + + zms.verifyAuthorizedServiceOperation("sports.hockey", "putrole"); + zms.verifyAuthorizedServiceOperation("sports.hockey", "putpolicy"); + zms.verifyAuthorizedServiceOperation("sports.hockey", "deleterole"); + zms.verifyAuthorizedServiceOperation("sports.hockey", "putserviceidentity"); + + // ATHENZ-1528 + // Try passing along operationItem key + value to see if verification works + + // First, try with AllowAll operation + zms.verifyAuthorizedServiceOperation("coretech.newsvc", "putrole"); // putrole has no restriction. This should pass. + + // Second, try with restricted operation. Currently, putmembership only allow single operation item. + zms.verifyAuthorizedServiceOperation("coretech.newsvc", "putmembership", "role", "platforms_deployer"); + zms.verifyAuthorizedServiceOperation("coretech.newsvc", "putmembership", "role", "platforms_different_deployer"); + zms.verifyAuthorizedServiceOperation("coretech.newsvc", "putmembership", "not_role", "platforms_role_deployer"); + + // Third, try with restriction operation, with not-specified operation item. + boolean errorThrown = false; + int code = -1; + try { + zms.verifyAuthorizedServiceOperation("coretech.newsvc", "putmembership", "role", "platforms_deployer_new"); + } catch (ResourceException ex) { + errorThrown = true; + code = ex.getCode(); + } + assertEquals(403, code); + assertTrue(errorThrown); + errorThrown = false; + code = -1; + + try { + zms.verifyAuthorizedServiceOperation("coretech.newsvc", "putmembership", "not_role", "platforms_deployer_new_new"); + } catch (ResourceException ex) { + errorThrown = true; + code = ex.getCode(); + } + assertEquals(403, code); + assertTrue(errorThrown); + errorThrown = false; + code = -1; + + + try { + zms.verifyAuthorizedServiceOperation("coretech.storage2", "postdomain"); + } catch (ResourceException ex) { + errorThrown = true; + code = ex.getCode(); + } + assertEquals(403, code); + assertTrue(errorThrown); + errorThrown = false; + code = -1; + + try { + zms.verifyAuthorizedServiceOperation("media.storage", "deleterole"); + } catch (ResourceException ex) { + errorThrown = true; + code = ex.getCode(); + } + assertEquals(403, code); + assertTrue(errorThrown); + errorThrown = false; + code = -1; + } + + @Test + public void testPutProviderResourceGroupRoles() { + + String tenantDomain = "putproviderresourcegrouproles"; + TopLevelDomain dom = createTopLevelDomainObject(tenantDomain, "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + List roleActions = new ArrayList(); + for (Struct.Field f : RESOURCE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + String providerService = "storage"; + String providerDomain = "coretech"; + String resourceGroup = "hockey"; + + ProviderResourceGroupRoles providerRoles = new ProviderResourceGroupRoles() + .setDomain(providerDomain).setService(providerService) + .setTenant(tenantDomain).setRoles(roleActions) + .setResourceGroup(resourceGroup); + zms.putProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomain, providerDomain, providerService, + resourceGroup, auditRef, providerRoles); + + ProviderResourceGroupRoles tRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, providerDomain, providerService, resourceGroup); + + assertNotNull(tRoles); + assertEquals(providerDomain.toLowerCase(), tRoles.getDomain()); + assertEquals(providerService.toLowerCase(), tRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), tRoles.getTenant()); + assertEquals(resourceGroup.toLowerCase(), tRoles.getResourceGroup()); + assertEquals(RESOURCE_PROVIDER_ROLE_ACTIONS.size(), tRoles.getRoles().size()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + } + + @Test + public void testPutProviderResourceGroupMultipleRoles() { + + String tenantDomain = "putproviderresourcegroupmultipleroles"; + TopLevelDomain dom = createTopLevelDomainObject(tenantDomain, "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + List roleActions = new ArrayList(); + for (Struct.Field f : RESOURCE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + String providerService = "storage"; + String providerDomain = "coretech"; + String resourceGroup1 = "hockey"; + String resourceGroup2 = "baseball"; + + // add resource group1 roles + + ProviderResourceGroupRoles providerRoles = new ProviderResourceGroupRoles() + .setDomain(providerDomain).setService(providerService) + .setTenant(tenantDomain).setRoles(roleActions) + .setResourceGroup(resourceGroup1); + zms.putProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomain, providerDomain, providerService, + resourceGroup1, auditRef, providerRoles); + + // add resource group2 roles + + providerRoles = new ProviderResourceGroupRoles() + .setDomain(providerDomain).setService(providerService) + .setTenant(tenantDomain).setRoles(roleActions) + .setResourceGroup(resourceGroup1); + zms.putProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomain, providerDomain, providerService, + resourceGroup2, auditRef, providerRoles); + + // verify group 1 roles + + ProviderResourceGroupRoles tRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, providerDomain, providerService, resourceGroup1); + + assertNotNull(tRoles); + assertEquals(providerDomain.toLowerCase(), tRoles.getDomain()); + assertEquals(providerService.toLowerCase(), tRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), tRoles.getTenant()); + assertEquals(resourceGroup1.toLowerCase(), tRoles.getResourceGroup()); + assertEquals(RESOURCE_PROVIDER_ROLE_ACTIONS.size(), tRoles.getRoles().size()); + + // verify group 2 roles + + tRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, providerDomain, providerService, resourceGroup2); + + assertNotNull(tRoles); + assertEquals(providerDomain.toLowerCase(), tRoles.getDomain()); + assertEquals(providerService.toLowerCase(), tRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), tRoles.getTenant()); + assertEquals(resourceGroup2.toLowerCase(), tRoles.getResourceGroup()); + assertEquals(RESOURCE_PROVIDER_ROLE_ACTIONS.size(), tRoles.getRoles().size()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + } + + @Test + public void testDeleteProviderResourceGroupRoles() { + + String tenantDomain = "deleteproviderresourcegrouproles"; + TopLevelDomain dom = createTopLevelDomainObject(tenantDomain, "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + List roleActions = new ArrayList(); + for (Struct.Field f : RESOURCE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + String providerService = "storage"; + String providerDomain = "coretech"; + String resourceGroup = "hockey"; + + ProviderResourceGroupRoles providerRoles = new ProviderResourceGroupRoles() + .setDomain(providerDomain).setService(providerService) + .setTenant(tenantDomain).setRoles(roleActions) + .setResourceGroup(resourceGroup); + zms.putProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomain, providerDomain, providerService, + resourceGroup, auditRef, providerRoles); + + ProviderResourceGroupRoles tRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, providerDomain, providerService, resourceGroup); + + assertNotNull(tRoles); + assertEquals(providerDomain.toLowerCase(), tRoles.getDomain()); + assertEquals(providerService.toLowerCase(), tRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), tRoles.getTenant()); + assertEquals(resourceGroup.toLowerCase(), tRoles.getResourceGroup()); + assertEquals(RESOURCE_PROVIDER_ROLE_ACTIONS.size(), tRoles.getRoles().size()); + + // now let's delete our resource group + + zms.deleteProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomain, providerDomain, providerService, + resourceGroup, auditRef); + + // now let's retrieve our resource group and verify we got 0 roles + + tRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, providerDomain, providerService, resourceGroup); + + assertNotNull(tRoles); + assertEquals(providerDomain.toLowerCase(), tRoles.getDomain()); + assertEquals(providerService.toLowerCase(), tRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), tRoles.getTenant()); + assertEquals(resourceGroup.toLowerCase(), tRoles.getResourceGroup()); + assertEquals(0, tRoles.getRoles().size()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + } + + @Test + public void testDeleteProviderResourceGroupMultipleRoles() { + + String tenantDomain = "deleteproviderresourcegroupmultipleroles"; + TopLevelDomain dom = createTopLevelDomainObject(tenantDomain, "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + List roleActions = new ArrayList(); + for (Struct.Field f : RESOURCE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + String providerService = "storage"; + String providerDomain = "coretech"; + String resourceGroup1 = "hockey"; + String resourceGroup2 = "baseball"; + + // add resource group1 roles + + ProviderResourceGroupRoles providerRoles = new ProviderResourceGroupRoles() + .setDomain(providerDomain).setService(providerService) + .setTenant(tenantDomain).setRoles(roleActions) + .setResourceGroup(resourceGroup1); + zms.putProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomain, providerDomain, providerService, + resourceGroup1, auditRef, providerRoles); + + // add resource group2 roles + + providerRoles = new ProviderResourceGroupRoles() + .setDomain(providerDomain).setService(providerService) + .setTenant(tenantDomain).setRoles(roleActions) + .setResourceGroup(resourceGroup1); + zms.putProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomain, providerDomain, providerService, + resourceGroup2, auditRef, providerRoles); + + // now let's delete our resource group 1 + + zms.deleteProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomain, providerDomain, providerService, + resourceGroup1, auditRef); + + // verify group 1 roles and it's size of 0 + + ProviderResourceGroupRoles tRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, providerDomain, providerService, resourceGroup1); + + assertNotNull(tRoles); + assertEquals(providerDomain.toLowerCase(), tRoles.getDomain()); + assertEquals(providerService.toLowerCase(), tRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), tRoles.getTenant()); + assertEquals(resourceGroup1.toLowerCase(), tRoles.getResourceGroup()); + assertEquals(0, tRoles.getRoles().size()); + + // verify group 2 roles with valid size of roles + + tRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, providerDomain, providerService, resourceGroup2); + + assertNotNull(tRoles); + assertEquals(providerDomain.toLowerCase(), tRoles.getDomain()); + assertEquals(providerService.toLowerCase(), tRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), tRoles.getTenant()); + assertEquals(resourceGroup2.toLowerCase(), tRoles.getResourceGroup()); + assertEquals(RESOURCE_PROVIDER_ROLE_ACTIONS.size(), tRoles.getRoles().size()); + + // now let's delete our resource group 2 + + zms.deleteProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomain, providerDomain, providerService, + resourceGroup2, auditRef); + + // now both get operations must return 0 for the size + + tRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, providerDomain, providerService, resourceGroup1); + + assertNotNull(tRoles); + assertEquals(0, tRoles.getRoles().size()); + + tRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, providerDomain, providerService, resourceGroup2); + + assertNotNull(tRoles); + assertEquals(0, tRoles.getRoles().size()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + } + + @Test + public void testGetProviderResourceGroupRoles() { + + String tenantDomain = "getproviderresourcegrouproles"; + TopLevelDomain dom = createTopLevelDomainObject(tenantDomain, "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + List roleActions = new ArrayList(); + for (Struct.Field f : RESOURCE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + String providerService = "storage"; + String providerDomain = "coretech"; + String resourceGroup = "hockey"; + + ProviderResourceGroupRoles providerRoles = new ProviderResourceGroupRoles() + .setDomain(providerDomain).setService(providerService) + .setTenant(tenantDomain).setRoles(roleActions) + .setResourceGroup(resourceGroup); + zms.putProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomain, providerDomain, providerService, + resourceGroup, auditRef, providerRoles); + + ProviderResourceGroupRoles tRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, providerDomain, providerService, resourceGroup); + + assertNotNull(tRoles); + assertEquals(providerDomain.toLowerCase(), tRoles.getDomain()); + assertEquals(providerService.toLowerCase(), tRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), tRoles.getTenant()); + assertEquals(resourceGroup.toLowerCase(), tRoles.getResourceGroup()); + assertEquals(RESOURCE_PROVIDER_ROLE_ACTIONS.size(), tRoles.getRoles().size()); + List traList = tRoles.getRoles(); + List roles = new ArrayList<>(); + for (TenantRoleAction ra : traList) { + roles.add(ra.getRole()); + } + assertTrue(roles.contains("reader")); + assertTrue(roles.contains("writer")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + } + + @Test + public void testGetProviderResourceGroupRolesInvalid() { + + String tenantDomain = "getproviderresourcegrouprolesinvalid"; + TopLevelDomain dom = createTopLevelDomainObject(tenantDomain, "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom); + + // all invalid input with provider domain, resource and resource group + // just returns an empty list for role actions. + + ProviderResourceGroupRoles tRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, "test1", "invalid", "hockey"); + + assertNotNull(tRoles); + assertEquals(0, tRoles.getRoles().size()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + } + + @Test + public void testPutProviderResourceGroupRolesWithAuthorizedService() { + + String tenantDomain = "providerresourcegrouprolesauthorizedservice"; + String providerService = "storage"; + String providerDomain = "coretech"; + String resourceGroup = "hockey"; + + setupTenantDomainProviderService(tenantDomain, providerDomain, providerService, + "http://localhost:8090/tableprovider"); + + // tenant is setup so let's setup up policy to authorize access to tenants + // without this role/policy we won't be authorized to add tenant roles + // to the provider domain even with authorized service details + + Role role = createRoleObject(providerDomain, "self_serve", null, + providerDomain + "." + providerService, null); + zms.putRole(mockDomRsrcCtx, providerDomain, "self_serve", auditRef, role); + + Policy policy = createPolicyObject(providerDomain, "self_serve", + "self_serve", "update", providerDomain + ":tenant.*", AssertionEffect.ALLOW); + zms.putPolicy(mockDomRsrcCtx, providerDomain, "self_serve", auditRef, policy); + + // now we're going to setup our provider role call + + List roleActions = new ArrayList(); + for (Struct.Field f : RESOURCE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + + ProviderResourceGroupRoles providerRoles = new ProviderResourceGroupRoles() + .setDomain(providerDomain).setService(providerService) + .setTenant(tenantDomain).setRoles(roleActions) + .setResourceGroup(resourceGroup); + + // we are going to create a principal object with authorized service + // set to coretech.storage + + String userId = "user1"; + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + String unsignedCreds = "v=U1;d=user;n=" + userId; + Principal principal = SimplePrincipal.create("user", userId, unsignedCreds + ";s=signature", + 0, principalAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(unsignedCreds); + ((SimplePrincipal) principal).setUnsignedCreds(unsignedCreds); + ((SimplePrincipal) principal).setAuthorizedService("coretech.storage"); + ResourceContext ctx = createResourceContext(principal); + + // after this call we should have roles set for both provider and tenant + + zms.putProviderResourceGroupRoles(ctx, tenantDomain, providerDomain, providerService, + resourceGroup, auditRef, providerRoles); + + ProviderResourceGroupRoles pRoles = zms.getProviderResourceGroupRoles(ctx, + tenantDomain, providerDomain, providerService, resourceGroup); + + assertNotNull(pRoles); + assertEquals(providerDomain.toLowerCase(), pRoles.getDomain()); + assertEquals(providerService.toLowerCase(), pRoles.getService()); + assertEquals(tenantDomain.toLowerCase(), pRoles.getTenant()); + assertEquals(resourceGroup.toLowerCase(), pRoles.getResourceGroup()); + assertEquals(RESOURCE_PROVIDER_ROLE_ACTIONS.size(), pRoles.getRoles().size()); + List traList = pRoles.getRoles(); + List roles = new ArrayList<>(); + for (TenantRoleAction ra : traList) { + roles.add(ra.getRole()); + } + assertTrue(roles.contains("reader")); + assertTrue(roles.contains("writer")); + + // now get the tenant roles for the provider + + TenantResourceGroupRoles tRoles = zms.getTenantResourceGroupRoles(mockDomRsrcCtx, providerDomain, + providerService, tenantDomain, resourceGroup); + assertNotNull(tRoles); + assertEquals(tRoles.getDomain(), providerDomain); + assertEquals(tRoles.getService(), providerService); + assertEquals(tRoles.getTenant(), tenantDomain); + assertEquals(tRoles.getResourceGroup(), resourceGroup); + assertEquals(RESOURCE_PROVIDER_ROLE_ACTIONS.size(), tRoles.getRoles().size()); + traList = pRoles.getRoles(); + roles = new ArrayList<>(); + for (TenantRoleAction ra : traList) { + roles.add(ra.getRole()); + } + assertTrue(roles.contains("reader")); + assertTrue(roles.contains("writer")); + + // now we're going to delete the provider roles using the standard + // resource object without the authorized service. in this case + // the provider roles are going to be deleted but not the tenant + // roles from the provider domain + + zms.deleteProviderResourceGroupRoles(mockDomRsrcCtx, tenantDomain, providerDomain, + providerService, resourceGroup, auditRef); + + // so for tenant we're going to 0 provider roles + + pRoles = zms.getProviderResourceGroupRoles(mockDomRsrcCtx, + tenantDomain, providerDomain, providerService, resourceGroup); + + assertNotNull(pRoles); + assertEquals(0, pRoles.getRoles().size()); + + // but for provider we're still going to get full set of roles + + tRoles = zms.getTenantResourceGroupRoles(mockDomRsrcCtx, providerDomain, + providerService, tenantDomain, resourceGroup); + assertNotNull(tRoles); + assertEquals(2, tRoles.getRoles().size()); + + // now this time we're going to delete with the principal with the + // authorized service token + + zms.deleteProviderResourceGroupRoles(ctx, tenantDomain, providerDomain, + providerService, resourceGroup, auditRef); + + // so for tenant we're still going to 0 provider roles + + pRoles = zms.getProviderResourceGroupRoles(ctx, + tenantDomain, providerDomain, providerService, resourceGroup); + + assertNotNull(pRoles); + assertEquals(0, pRoles.getRoles().size()); + + // and for provider we're now going to get 0 tenant roles as well + + tRoles = zms.getTenantResourceGroupRoles(mockDomRsrcCtx, providerDomain, + providerService, tenantDomain, resourceGroup); + assertNotNull(tRoles); + assertEquals(0, tRoles.getRoles().size()); + + // clean up our domains + + zms.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + } + + @Test + public void testProviderResourceGroupRolesWithAuthorizedServiceNoAccess() { + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_putprovrsrcdomnoaccess"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String tenantDomain = "provrscgrprolesauthorizedservicenoaccess"; + String providerService = "index"; + String providerDomain = "coretech"; + String resourceGroup = "hockey"; + + setupTenantDomainProviderService(zmsImpl, tenantDomain, providerDomain, providerService, + "http://localhost:8090/tableprovider"); + + // tenant is setup so let's setup up policy to authorize access to tenants + // without this role/policy we won't be authorized to add tenant roles + // to the provider domain even with authorized service details + + Role role = createRoleObject(providerDomain, "self_serve", null, + providerDomain + "." + providerService, null); + zmsImpl.putRole(mockDomRsrcCtx, providerDomain, "self_serve", auditRef, role); + + Policy policy = createPolicyObject(providerDomain, "self_serve", + "self_serve", "update", providerDomain + ":tenant.*", AssertionEffect.ALLOW); + zmsImpl.putPolicy(mockDomRsrcCtx, providerDomain, "self_serve", auditRef, policy); + + // now we're going to setup our provider role call + + List roleActions = new ArrayList(); + for (Struct.Field f : RESOURCE_PROVIDER_ROLE_ACTIONS) { + roleActions.add(new TenantRoleAction().setRole(f.name()).setAction( + (String) f.value())); + } + + ProviderResourceGroupRoles providerRoles = new ProviderResourceGroupRoles() + .setDomain(providerDomain).setService(providerService) + .setTenant(tenantDomain).setRoles(roleActions) + .setResourceGroup(resourceGroup); + + // we are going to create a principal object with authorized service + // set to coretech.index + + String userId = "user1"; + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + String unsignedCreds = "v=U1;d=user;n=" + userId; + Principal principal = SimplePrincipal.create("user", userId, unsignedCreds + ";s=signature", + 0, principalAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(unsignedCreds); + ((SimplePrincipal) principal).setUnsignedCreds(unsignedCreds); + ((SimplePrincipal) principal).setAuthorizedService("coretech.index"); + ResourceContext ctx = createResourceContext(principal); + + // this call should return an exception since we can't execute + // the putproviderresourcegrouproles operation with our chained token + + try { + zmsImpl.putProviderResourceGroupRoles(ctx, tenantDomain, providerDomain, providerService, + resourceGroup, auditRef, providerRoles); + fail(); + } catch (ResourceException ex) { + assertEquals(403, ex.getCode()); + } + + String caller = "putproviderresourcegrouproles"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + assertTrue(msg.indexOf("WHAT-details=(ERROR=(Unauthorized Operation (putproviderresourcegrouproles) for Service coretech.index)") != -1); + foundError = true; + break; + } + assertTrue(foundError); + + // clean up our domains + + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, tenantDomain, auditRef); + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, providerDomain, auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + + @Test + public void testOptionsUserTokenInvalidService() { + + // null service must return 400 + + try { + zms.optionsUserToken(mockDomRsrcCtx, "user1", null); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + // empty service must return 400 + + try { + zms.optionsUserToken(mockDomRsrcCtx, "user1", ""); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + // unknown registered service must return 400 + try { + zms.optionsUserToken(mockDomRsrcCtx, "user1", "unknown_service_name"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + // in a list all services must be valid - any invalid must return 400 + + try { + zms.optionsUserToken(mockDomRsrcCtx, "user1", "coretech.storage,unknown_service_name"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + } + + @Test + public void testOptionsUserToken() { + HttpServletRequest servletRequest = new MockHttpServletRequest(); + HttpServletResponse servletResponse = new MockHttpServletResponse(); + ResourceContext ctx = new ZMSImpl.RsrcCtxWrapper(servletRequest, servletResponse, null, null); + + zms.optionsUserToken(ctx, "user", "coretech.storage"); + assertEquals("GET", servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_METHODS)); + assertEquals("2592000", servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_MAX_AGE)); + assertEquals("true", servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_CREDENTIALS)); + + // using default values where we'll get back * for origin and no allow headers + + assertEquals("*", servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_ORIGIN)); + assertNull(servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_HEADERS)); + } + + @Test + public void testOptionsUserTokenRequestHeaders() { + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + MockHttpServletResponse servletResponse = new MockHttpServletResponse(); + ResourceContext ctx = new ZMSImpl.RsrcCtxWrapper(servletRequest, servletResponse, null, null); + + String origin = "https://zms.origin.athenzcompany.com"; + String requestHeaders = "X-Forwarded-For,Content-Type"; + servletRequest.addHeader(ZMSConsts.HTTP_ORIGIN, origin); + servletRequest.addHeader(ZMSConsts.HTTP_ACCESS_CONTROL_REQUEST_HEADERS, requestHeaders); + + // this time we're going to try with multiple services + + zms.optionsUserToken(ctx, "user", "coretech.storage,coretech.index"); + assertEquals("GET", servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_METHODS)); + assertEquals("2592000", servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_MAX_AGE)); + assertEquals("true", servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_CREDENTIALS)); + + assertEquals(origin, servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_ORIGIN)); + assertEquals(requestHeaders, servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_HEADERS)); + } + + @Test + public void testSetStandardCORSHeaders() { + HttpServletRequest servletRequest = new MockHttpServletRequest(); + HttpServletResponse servletResponse = new MockHttpServletResponse(); + ResourceContext ctx = new ZMSImpl.RsrcCtxWrapper(servletRequest, servletResponse, null, null); + + zms.setStandardCORSHeaders(ctx); + assertEquals("true", servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_CREDENTIALS)); + + // using default values where we'll get back * for origin and no allow headers + + assertEquals("*", servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_ORIGIN)); + assertNull(servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_HEADERS)); + } + + @Test + public void testSetStandardCORSHeadersRequestHeaders() { + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + MockHttpServletResponse servletResponse = new MockHttpServletResponse(); + ResourceContext ctx = new ZMSImpl.RsrcCtxWrapper(servletRequest, servletResponse, null, null); + + String origin = "https://zms.origin.athenzcompany.com"; + String requestHeaders = "X-Forwarded-For,Content-Type"; + servletRequest.addHeader(ZMSConsts.HTTP_ORIGIN, origin); + servletRequest.addHeader(ZMSConsts.HTTP_ACCESS_CONTROL_REQUEST_HEADERS, requestHeaders); + + zms.setStandardCORSHeaders(ctx); + assertEquals("true", servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_CREDENTIALS)); + + assertEquals(origin, servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_ORIGIN)); + assertEquals(requestHeaders, servletResponse.getHeader(ZMSConsts.HTTP_ACCESS_CONTROL_ALLOW_HEADERS)); + } + + @Test + public void testVerifyProviderEndpoint() { + + // http successful test cases (localhost or *.athenzcompany.com) + assertTrue(zms.verifyProviderEndpoint("http://localhost")); + assertTrue(zms.verifyProviderEndpoint("http://localhost:4080")); + assertTrue(zms.verifyProviderEndpoint("http://localhost:4080/")); + assertTrue(zms.verifyProviderEndpoint("http://localhost:4080/test1")); + assertTrue(zms.verifyProviderEndpoint("http://host1.athenzcompany.com")); + assertTrue(zms.verifyProviderEndpoint("http://host1.athenzcompany.com:4080")); + assertTrue(zms.verifyProviderEndpoint("http://host1.athenzcompany.com:4080/")); + assertTrue(zms.verifyProviderEndpoint("http://host1.athenzcompany.com:4080/test1")); + + // https successful test cases (localhost or *.athenzcompany.com) + assertTrue(zms.verifyProviderEndpoint("https://localhost")); + assertTrue(zms.verifyProviderEndpoint("https://localhost:4080")); + assertTrue(zms.verifyProviderEndpoint("https://localhost:4080/")); + assertTrue(zms.verifyProviderEndpoint("https://localhost:4080/test1")); + assertTrue(zms.verifyProviderEndpoint("https://host1.athenzcompany.com")); + assertTrue(zms.verifyProviderEndpoint("https://host1.athenzcompany.com:4080")); + assertTrue(zms.verifyProviderEndpoint("https://host1.athenzcompany.com:4080/")); + assertTrue(zms.verifyProviderEndpoint("https://host1.athenzcompany.com:4080/test1")); + + // http invalid cases - not *.athenzcompany.com + assertFalse(zms.verifyProviderEndpoint("http://host1.server.com")); + assertFalse(zms.verifyProviderEndpoint("http://host1.server.com:4080")); + assertFalse(zms.verifyProviderEndpoint("http://host1.server.com:4080/")); + assertFalse(zms.verifyProviderEndpoint("http://host1.server.yahoo:4080/test1")); + assertFalse(zms.verifyProviderEndpoint("http://host1.athenz.server.com:4080/test1")); + assertFalse(zms.verifyProviderEndpoint("http://host1.athenz.ch:4080/test1")); + + // non-http scheme test cases + assertFalse(zms.verifyProviderEndpoint("file://host1.athenz.com")); + + // other null test cases + assertTrue(zms.verifyProviderEndpoint(null)); + } + + @Test + public void testGetServerTemplateList() { + + ServerTemplateList list = zms.getServerTemplateList(mockDomRsrcCtx); + assertNotNull(list); + assertTrue(list.getTemplateNames().contains("platforms")); + assertTrue(list.getTemplateNames().contains("vipng")); + assertTrue(list.getTemplateNames().contains("user_provisioning")); + } + + @Test + public void testGetTemplateInvalid() { + try { + zms.getTemplate(mockDomRsrcCtx, "platforms test"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + try { + zms.getTemplate(mockDomRsrcCtx, "invalid"); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + } + + @Test + public void testGetTemplate() { + + Template template = zms.getTemplate(mockDomRsrcCtx, "user_provisioning"); + assertNotNull(template); + + List roles = template.getRoles(); + assertNotNull(roles); + assertEquals(3, roles.size()); + + Role user_role = null; + Role superuser_role = null; + Role openstack_readers_role = null; + for (Role role : roles) { + if (role.getName().equals("_domain_:role.user")) { + user_role = role; + } else if (role.getName().equals("_domain_:role.superuser")) { + superuser_role = role; + } else if (role.getName().equals("_domain_:role.openstack_readers")) { + openstack_readers_role = role; + } + } + + assertNotNull(user_role); + assertNotNull(superuser_role); + assertNotNull(openstack_readers_role); + + // openstack_readers role has 2 members + + assertEquals(2, openstack_readers_role.getMembers().size()); + assertTrue(openstack_readers_role.getMembers().contains("sys.builder")); + assertTrue(openstack_readers_role.getMembers().contains("sys.openstack")); + + // other roles have no members + + assertNull(user_role.getMembers()); + assertNull(superuser_role.getMembers()); + + List policies = template.getPolicies(); + assertNotNull(policies); + assertEquals(3, policies.size()); + + Policy user_policy = null; + Policy superuser_policy = null; + Policy openstack_readers_policy = null; + for (Policy policy : policies) { + if (policy.getName().equals("_domain_:policy.user")) { + user_policy = policy; + } else if (policy.getName().equals("_domain_:policy.superuser")) { + superuser_policy = policy; + } else if (policy.getName().equals("_domain_:policy.openstack_readers")) { + openstack_readers_policy = policy; + } + } + + assertNotNull(user_policy); + assertNotNull(superuser_policy); + assertNotNull(openstack_readers_policy); + + assertEquals(1, user_policy.getAssertions().size()); + assertEquals(1, superuser_policy.getAssertions().size()); + assertEquals(2, openstack_readers_policy.getAssertions().size()); + + template = zms.getTemplate(mockDomRsrcCtx, "vipng"); + assertNotNull(template); + + template = zms.getTemplate(mockDomRsrcCtx, "platforms"); + assertNotNull(template); + + template = zms.getTemplate(mockDomRsrcCtx, "VipNg"); + assertNotNull(template); + } + + @Test + public void testValidateSolutionTemplates() { + final String caller = "testValidateDomainTemplates"; + List templateNames = new ArrayList<>(); + templateNames.add("platforms"); + zms.validateSolutionTemplates(templateNames, caller); + + templateNames.add("vipng"); + zms.validateSolutionTemplates(templateNames, caller); + + templateNames.add("athenz"); + try { + zms.validateSolutionTemplates(templateNames, caller); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + assertTrue(ex.getMessage().contains("athenz")); + } + } + + @Test + public void testPutDomainTemplateInvalidTemplate() { + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + String storeDir = ZMS_DATA_STORE_PATH + "_putdomtempllistinvalid"; + ZMSImpl zmsImpl = getZmsImpl(storeDir, alogger); + + String domainName = "templatelist-invalid"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zmsImpl.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + DomainTemplate templateList = new DomainTemplate(); + List templates = new ArrayList<>(); + templates.add("test validate"); + templateList.setTemplateNames(templates); + try { + zmsImpl.putDomainTemplate(mockDomRsrcCtx, domainName, auditRef, templateList); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + String caller = "putdomaintemplate"; + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(" + caller + ")") == -1) { + continue; + } + assertTrue(msg.indexOf("CLIENT-IP=(" + MOCKCLIENTADDR + ")") != -1); + int index = msg.indexOf("WHAT-details=(ERROR=(Invalid DomainTemplate error: String pattern mismatch (expected \"[a-zA-Z0-9_][a-zA-Z0-9_-]*\") for type SimpleName in data[0]"); + assertTrue(index != -1); + int index2 = msg.indexOf("templates=(\"test validate\"))"); + assertTrue(index < index2); + foundError = true; + break; + } + assertTrue(foundError); + + zmsImpl.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + FileConnection.deleteDirectory(new File(storeDir)); + } + + @Test + public void testPutDomainTemplateNotFoundTemplate() { + + String domainName = "templatelist-invalid"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + DomainTemplate templateList = new DomainTemplate(); + List templates = new ArrayList<>(); + templates.add("InvalidTemplate"); + templateList.setTemplateNames(templates); + try { + zms.putDomainTemplate(mockDomRsrcCtx, domainName, auditRef, templateList); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testPutDomainTemplateSingleTemplate() { + + String domainName = "templatelist-single"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + DomainTemplate domTemplate = new DomainTemplate(); + List templates = new ArrayList<>(); + templates.add("vipng"); + domTemplate.setTemplateNames(templates); + + zms.putDomainTemplate(mockDomRsrcCtx, domainName, auditRef, domTemplate); + + // verify that our role collection includes the roles defined in template + + List names = zms.dbService.listRoles(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + Role role = zms.dbService.getRole(domainName, "vip_admin", false, false); + assertEquals(domainName + ":role.vip_admin", role.getName()); + assertNull(role.getTrust()); + assertNull(role.getMembers()); + + role = zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false); + assertEquals(domainName + ":role.sys_network_super_vip_admin", role.getName()); + assertEquals("sys.network", role.getTrust()); + + // verify that our policy collections includes the policies defined in the template + + names = zms.dbService.listPolicies(domainName); + assertEquals(3, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + + Policy policy = zms.dbService.getPolicy(domainName, "vip_admin"); + assertEquals(domainName + ":policy.vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + Assertion assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + policy = zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin"); + assertEquals(domainName + ":policy.sys_network_super_vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.sys_network_super_vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + // delete an applied service template + // + String templateName = "vipng"; + zms.deleteDomainTemplate(mockDomRsrcCtx, domainName, templateName, auditRef); + + // verify that our role collection does NOT include the roles defined in template + + names = zms.dbService.listRoles(domainName); + assertEquals(1, names.size()); + assertTrue(names.contains("admin")); + + names = zms.dbService.listPolicies(domainName); + assertEquals(1, names.size()); + assertTrue(names.contains("admin")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testPutDomainTemplateMultipleTemplates() { + + String domainName = "templatelist-multiple"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + DomainTemplate domTemplate = new DomainTemplate(); + List templates = new ArrayList<>(); + templates.add("vipng"); + templates.add("platforms"); + templates.add("user_provisioning"); + domTemplate.setTemplateNames(templates); + + zms.putDomainTemplate(mockDomRsrcCtx, domainName, auditRef, domTemplate); + + // verify that our role collection includes the roles defined in template + + List names = zms.dbService.listRoles(domainName); + assertEquals(7, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + assertTrue(names.contains("platforms_deployer")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + Role role = zms.dbService.getRole(domainName, "openstack_readers", false, false); + assertEquals(domainName + ":role.openstack_readers", role.getName()); + assertNull(role.getTrust()); + assertEquals(2, role.getMembers().size()); + assertTrue(role.getMembers().contains("sys.builder")); + assertTrue(role.getMembers().contains("sys.openstack")); + + role = zms.dbService.getRole(domainName, "sys_network_super_vip_admin", false, false); + assertEquals(domainName + ":role.sys_network_super_vip_admin", role.getName()); + assertEquals("sys.network", role.getTrust()); + + // verify that our policy collections includes the policies defined in the template + + names = zms.dbService.listPolicies(domainName); + assertEquals(7, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + assertTrue(names.contains("platforms_deploy")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + Policy policy = zms.dbService.getPolicy(domainName, "vip_admin"); + assertEquals(domainName + ":policy.vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + Assertion assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + policy = zms.dbService.getPolicy(domainName, "sys_network_super_vip_admin"); + assertEquals(domainName + ":policy.sys_network_super_vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(domainName + ":role.sys_network_super_vip_admin", assertion.getRole()); + assertEquals(domainName + ":vip*", assertion.getResource()); + + // delete applied service template + // + String templateName = "vipng"; + zms.deleteDomainTemplate(mockDomRsrcCtx, domainName, templateName, auditRef); + + // verify that our role collection does NOT include the vipng roles defined in template + + names = zms.dbService.listRoles(domainName); + assertEquals(5, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("platforms_deployer")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + names = zms.dbService.listPolicies(domainName); + assertEquals(5, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("platforms_deploy")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + // delete applied service template + // + templateName = "platforms"; + zms.deleteDomainTemplate(mockDomRsrcCtx, domainName, templateName, auditRef);; + + // verify that our role collection does NOT include the platforms roles defined in template + + names = zms.dbService.listRoles(domainName); + assertEquals(4, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + names = zms.dbService.listPolicies(domainName); + assertEquals(4, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + // delete last applied service template + // + templateName = "user_provisioning"; + zms.deleteDomainTemplate(mockDomRsrcCtx, domainName, templateName, auditRef); + + // verify that our role collection does NOT include the user_provisioning roles defined in template + + names = zms.dbService.listRoles(domainName); + assertEquals(1, names.size()); + assertTrue(names.contains("admin")); + + names = zms.dbService.listPolicies(domainName); + assertEquals(1, names.size()); + assertTrue(names.contains("admin")); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetDomainTemplateListInvalid() { + + try { + zms.getDomainTemplateList(mockDomRsrcCtx, "invalid_domain name"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + try { + zms.getDomainTemplateList(mockDomRsrcCtx, "not_found_domain_name"); + fail(); + } catch (ResourceException ex) { + assertEquals(404, ex.getCode()); + } + } + + @Test + public void testGetDomainTemplateList() { + + String domainName = "domaintemplatelist-valid"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + // initially no templates + + DomainTemplateList domaintemplateList = zms.getDomainTemplateList(mockDomRsrcCtx, domainName); + List templates = domaintemplateList.getTemplateNames(); + assertEquals(0, templates.size()); + + // add a single template + + DomainTemplate domTemplate = new DomainTemplate(); + templates = new ArrayList<>(); + templates.add("vipng"); + domTemplate.setTemplateNames(templates); + + zms.putDomainTemplate(mockDomRsrcCtx, domainName, auditRef, domTemplate); + + domaintemplateList = zms.getDomainTemplateList(mockDomRsrcCtx, domainName); + templates = domaintemplateList.getTemplateNames(); + assertEquals(1, templates.size()); + assertTrue(templates.contains("vipng")); + + // add 2 templates + + domTemplate = new DomainTemplate(); + templates = new ArrayList<>(); + templates.add("vipng"); + templates.add("platforms"); + domTemplate.setTemplateNames(templates); + + zms.putDomainTemplate(mockDomRsrcCtx, domainName, auditRef, domTemplate); + + domaintemplateList = zms.getDomainTemplateList(mockDomRsrcCtx, domainName); + templates = domaintemplateList.getTemplateNames(); + assertEquals(2, templates.size()); + assertTrue(templates.contains("vipng")); + assertTrue(templates.contains("platforms")); + + // add the same set of templates again and no change in results + domTemplate = new DomainTemplate(); + domTemplate.setTemplateNames(templates); + zms.putDomainTemplate(mockDomRsrcCtx, domainName, auditRef, domTemplate); + + domaintemplateList = zms.getDomainTemplateList(mockDomRsrcCtx, domainName); + templates = domaintemplateList.getTemplateNames(); + assertEquals(2, templates.size()); + assertTrue(templates.contains("vipng")); + assertTrue(templates.contains("platforms")); + + // delete an applied service template + // + String templateName = "vipng"; + zms.deleteDomainTemplate(mockDomRsrcCtx, domainName, templateName, auditRef); + + domaintemplateList = zms.getDomainTemplateList(mockDomRsrcCtx, domainName); + templates = domaintemplateList.getTemplateNames(); + assertEquals(1, templates.size()); + assertTrue(templates.contains("platforms")); + + // delete last applied service template + // + templateName = "platforms"; + zms.deleteDomainTemplate(mockDomRsrcCtx, domainName, templateName, auditRef); + + domaintemplateList = zms.getDomainTemplateList(mockDomRsrcCtx, domainName); + templates = domaintemplateList.getTemplateNames(); + assertTrue(templates.isEmpty()); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testPostSubDomainWithTemplates() { + + String domainName = "postsubdomain-withtemplate"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + SubDomain dom2 = createSubDomainObject("sub", domainName, + "Test Domain2", "testOrg", adminUser); + DomainTemplateList templateList = new DomainTemplateList(); + List templates = new ArrayList<>(); + templates.add("vipng"); + templates.add("platforms"); + templates.add("user_provisioning"); + templateList.setTemplateNames(templates); + dom2.setTemplates(templateList); + + Domain resDom1 = zms.postSubDomain(mockDomRsrcCtx, domainName, auditRef, dom2); + assertNotNull(resDom1); + + String subDomainName = domainName + ".sub"; + + // verify that our role collection includes the roles defined in template + + List names = zms.dbService.listRoles(subDomainName); + assertEquals(7, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + assertTrue(names.contains("platforms_deployer")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + Role role = zms.dbService.getRole(subDomainName, "openstack_readers", false, false); + assertEquals(subDomainName + ":role.openstack_readers", role.getName()); + assertNull(role.getTrust()); + assertEquals(2, role.getMembers().size()); + assertTrue(role.getMembers().contains("sys.builder")); + assertTrue(role.getMembers().contains("sys.openstack")); + + role = zms.dbService.getRole(subDomainName, "sys_network_super_vip_admin", false, false); + assertEquals(subDomainName + ":role.sys_network_super_vip_admin", role.getName()); + assertEquals("sys.network", role.getTrust()); + + // verify that our policy collections includes the policies defined in the template + + names = zms.dbService.listPolicies(subDomainName); + assertEquals(7, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("vip_admin")); + assertTrue(names.contains("sys_network_super_vip_admin")); + assertTrue(names.contains("platforms_deploy")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + Policy policy = zms.dbService.getPolicy(subDomainName, "vip_admin"); + assertEquals(subDomainName + ":policy.vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + Assertion assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(subDomainName + ":role.vip_admin", assertion.getRole()); + assertEquals(subDomainName + ":vip*", assertion.getResource()); + + policy = zms.dbService.getPolicy(subDomainName, "sys_network_super_vip_admin"); + assertEquals(subDomainName + ":policy.sys_network_super_vip_admin", policy.getName()); + assertEquals(1, policy.getAssertions().size()); + assertion = policy.getAssertions().get(0); + assertEquals("*", assertion.getAction()); + assertEquals(subDomainName + ":role.sys_network_super_vip_admin", assertion.getRole()); + assertEquals(subDomainName + ":vip*", assertion.getResource()); + + // verify the saved domain list + + DomainTemplateList domaintemplateList = zms.getDomainTemplateList(mockDomRsrcCtx, subDomainName); + templates = domaintemplateList.getTemplateNames(); + assertEquals(3, templates.size()); + assertTrue(templates.contains("vipng")); + assertTrue(templates.contains("platforms")); + assertTrue(templates.contains("user_provisioning")); + + // delete an applied service template + // + String templateName = "vipng"; + zms.deleteDomainTemplate(mockDomRsrcCtx, subDomainName, templateName, auditRef); + + domaintemplateList = zms.getDomainTemplateList(mockDomRsrcCtx, subDomainName); + templates = domaintemplateList.getTemplateNames(); + assertEquals(2, templates.size()); + assertTrue(templates.contains("platforms")); + assertTrue(templates.contains("user_provisioning")); + + names = zms.dbService.listRoles(subDomainName); + assertEquals(5, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("platforms_deployer")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + names = zms.dbService.listPolicies(subDomainName); + assertEquals(5, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("platforms_deploy")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + // delete an applied service template + // + templateName = "platforms"; + zms.deleteDomainTemplate(mockDomRsrcCtx, subDomainName, templateName, auditRef); + + domaintemplateList = zms.getDomainTemplateList(mockDomRsrcCtx, subDomainName); + templates = domaintemplateList.getTemplateNames(); + assertEquals(1, templates.size()); + assertTrue(templates.contains("user_provisioning")); + + names = zms.dbService.listRoles(subDomainName); + assertEquals(4, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + names = zms.dbService.listPolicies(subDomainName); + assertEquals(4, names.size()); + assertTrue(names.contains("admin")); + assertTrue(names.contains("user")); + assertTrue(names.contains("superuser")); + assertTrue(names.contains("openstack_readers")); + + // delete last applied service template + // + templateName = "user_provisioning"; + zms.deleteDomainTemplate(mockDomRsrcCtx, subDomainName, templateName, auditRef); + + domaintemplateList = zms.getDomainTemplateList(mockDomRsrcCtx, subDomainName); + templates = domaintemplateList.getTemplateNames(); + assertTrue(templates.isEmpty()); + + names = zms.dbService.listRoles(subDomainName); + assertEquals(1, names.size()); + assertTrue(names.contains("admin")); + + names = zms.dbService.listPolicies(subDomainName); + assertEquals(1, names.size()); + assertTrue(names.contains("admin")); + + zms.deleteSubDomain(mockDomRsrcCtx, domainName, "sub", auditRef); + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testPutPolicyNoLoopbackNoSuchDomainErrorAuditLog() { + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(servletRequest.getRemoteAddr()).thenReturn("10.10.10.11"); + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + + String storeDir = ZMS_DATA_STORE_PATH + "_al_noloop"; + ZMSImpl zmsObj = getZmsImpl(storeDir, alogger); + + String userId = "user"; + Principal principal = SimplePrincipal.create("user", userId, "v=U1;d=user;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal, servletRequest); + String domainName = "DomainName"; + String policyName = "PolicyName"; + + // Tests the putPolicy() condition: if (domain == null)... + try { + Policy policy = createPolicyObject(domainName, policyName); + + // should fail b/c we never created a top level domain. + zmsObj.putPolicy(context, domainName, policyName, auditRef, policy); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 404); + } + + FileConnection.deleteDirectory(new File(storeDir)); + + boolean foundError = false; + for (String msg: aLogMsgs) { + if (msg.indexOf("WHAT-api=(putpolicy)") == -1) { + continue; + } + assertTrue(msg, msg.indexOf("CLIENT-IP=(10.10.10.11)") != -1); + assertTrue(msg, msg.indexOf("WHAT-details=(ERROR=(putpolicy: Unknown domain: domainname))") != -1); + foundError = true; + break; + } + assertTrue(foundError); + } + + @Test + public void testPutPolicyLoopbackNoXFF_InconsistentNameErrorAuditLog() { + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(servletRequest.getRemoteAddr()).thenReturn("127.0.0.1"); + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + + String storeDir = ZMS_DATA_STORE_PATH + "_al_loopback"; + ZMSImpl zmsObj = getZmsImpl(storeDir, alogger); + + String userId = "user"; + Principal principal = SimplePrincipal.create("user", userId, "v=U1;d=user;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal, servletRequest); + String domainName = "DomainName"; + String policyName = "PolicyName"; + + // Tests the putPolicy() condition : if (!policyResourceName(domainName, policyName).equals(policy.getName()))... + try { + Policy policy = createPolicyObject(domainName, policyName); + + zmsObj.putPolicy(context, domainName, "Bad" + policyName, auditRef, policy); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } + + FileConnection.deleteDirectory(new File(storeDir)); + + boolean foundError = false; + for (String msg: aLogMsgs) { + if (!msg.contains("WHAT-api=(putpolicy)")) { + continue; + } + assertTrue(msg, msg.contains("CLIENT-IP=(127.0.0.1)")); + assertTrue(msg, msg.contains("WHAT-details=(ERROR=(putPolicy: Inconsistent policy names - expected: domainname:policy.badpolicyname, actual: domainname:policy.policyname)")); + foundError = true; + break; + } + assertTrue(foundError); + } + + @Test + public void testPutPolicyLoopbackXFFSingleValueAuditLog() { + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(servletRequest.getRemoteAddr()).thenReturn("127.0.0.1"); + Mockito.when(servletRequest.getHeader("X-Forwarded-For")).thenReturn("10.10.10.11"); + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + + String storeDir = ZMS_DATA_STORE_PATH + "_al_loopbackXff"; + ZMSImpl zmsObj = getZmsImpl(storeDir, alogger); + + String userId = "user"; + Principal principal = SimplePrincipal.create("user", userId, "v=U1;d=user;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal, servletRequest); + String domainName = "DomainName"; + String policyName = "PolicyName"; + + // Tests the putPolicy() condition : if (!policyResourceName(domainName, policyName).equals(policy.getName()))... + try { + Policy policy = createPolicyObject(domainName, policyName); + + zmsObj.putPolicy(context, domainName, "Bad" + policyName, auditRef, policy); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } + + FileConnection.deleteDirectory(new File(storeDir)); + + boolean foundError = false; + for (String msg: aLogMsgs) { + if (!msg.contains("WHAT-api=(putpolicy)")) { + continue; + } + assertTrue(msg, msg.contains("CLIENT-IP=(10.10.10.11)")); + assertTrue(msg, msg.contains("WHAT-entity=(badpolicyname)")); + assertTrue(msg, msg.contains("WHAT-details=(ERROR=(putPolicy: Inconsistent policy names - expected: domainname:policy.badpolicyname, actual: domainname:policy.policyname))")); + foundError = true; + break; + } + assertTrue(foundError); + } + + @Test + public void testPutPolicyLoopbackXFFMultipleValuesAuditLog() { + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(servletRequest.getRemoteAddr()).thenReturn("127.0.0.1"); + Mockito.when(servletRequest.getHeader("X-Forwarded-For")).thenReturn("10.10.10.11, 10.11.11.11, 10.12.12.12"); + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + + String storeDir = ZMS_DATA_STORE_PATH + "_al_loopbackXffMulti"; + ZMSImpl zmsObj = getZmsImpl(storeDir, alogger); + + String userId = "user"; + Principal principal = SimplePrincipal.create("user", userId, "v=U1;d=user;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal, servletRequest); + String domainName = "DomainName"; + String policyName = "PolicyName"; + + // Tests the putPolicy() condition : if (!policyResourceName(domainName, policyName).equals(policy.getName()))... + try { + Policy policy = createPolicyObject(domainName, policyName); + + zmsObj.putPolicy(context, domainName, "Bad" + policyName, auditRef, policy); + fail("requesterror not thrown."); + } catch (ResourceException e) { + assertEquals(e.getCode(), 400); + } + + FileConnection.deleteDirectory(new File(storeDir)); + + boolean foundError = false; + for (String msg: aLogMsgs) { + if (!msg.contains("WHAT-api=(putpolicy)")) { + continue; + } + assertTrue(msg, msg.contains("CLIENT-IP=(10.12.12.12)")); + assertTrue(msg, msg.contains("WHAT-entity=(badpolicyname)")); + int index = msg.indexOf("WHAT-details=(ERROR=(putPolicy: Inconsistent policy names - expected: domainname:policy.badpolicyname, actual: domainname:policy.policyname))"); + assertTrue(index != -1); + foundError = true; + break; + } + assertTrue(foundError); + } + + @Test + public void testRetrieveResourceDomainAssumeRoleWithTrust() { + assertEquals("trustdomain", zms.retrieveResourceDomain("resource", "assume_role", "trustdomain")); + } + + @Test + public void testRetrieveResourceDomainAssumeRoleWithOutTrust() { + assertEquals("domain1", zms.retrieveResourceDomain("domain1:resource", "assume_role", null)); + } + + @Test + public void testRetrieveResourceDomainValidDomain() { + assertEquals("domain1", zms.retrieveResourceDomain("domain1:resource", "read", null)); + assertEquals("domain1", zms.retrieveResourceDomain("domain1:resource", "read", "trustdomain")); + } + + @Test + public void testRetrieveResourceDomainInvalidResource() { + assertEquals(null, zms.retrieveResourceDomain("domain1:resource:invalid", "read", null)); + assertEquals(null, zms.retrieveResourceDomain("domain1:a:b:c:d:e", "read", "trustdomain")); + } + + @Test + public void testLoadPublicKeys() { + // verify that the public keys were loaded during server startup + assertFalse(zms.serverPublicKeyMap.isEmpty()); + String privKeyId = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_ID, "0"); + assertEquals(zms.serverPublicKeyMap.get(privKeyId), pubKey); + } + + @Test + public void testUnderscoreNotAllowed() { + + String domainName = "core-tech"; + String badDomainName = "core_tech"; + + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + TopLevelDomain dom2 = createTopLevelDomainObject(badDomainName, + "Test Domain1", "testOrg", adminUser); + try { + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom2); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + SubDomain sub = createSubDomainObject(badDomainName, domainName, + "Test Domain2", "testOrg", adminUser); + try { + zms.postSubDomain(mockDomRsrcCtx, domainName, auditRef, sub); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + UserDomain userDom = createUserDomainObject(badDomainName, "Test Domain1", "testOrg"); + try { + zms.postUserDomain(mockDomRsrcCtx, badDomainName, auditRef, userDom); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testReadOnlyMode() throws Exception { + + // first initialize our impl which would create our service + + ZMSImpl zmsTest = zmsInit(); + + // now we're going to create a new instance with read-only mode + + System.setProperty(ZMSConsts.ZMS_PROP_READ_ONLY_MODE, "true"); + + ObjectStore store = new FileObjectStore(new File(ZMS_DATA_STORE_PATH)); + String privKeyName = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY); + File privKeyFile = new File(privKeyName); + String privKey = Crypto.encodedFile(privKeyFile); + PrivateKey privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + + String privKeyId = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_ID, "0"); + + Metric metric = createMetric(); + zmsTest = new ZMSImpl("localhost", store, metric, privateKey, + privKeyId, pubKey, AuditLogFactory.getLogger(), null); + + TopLevelDomain dom1 = createTopLevelDomainObject("ReadOnlyDom1", + "Test Domain1", "testOrg", adminUser); + try { + zmsTest.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains("Read-Only")); + } + + Policy policy1 = createPolicyObject("ReadOnlyDom1", "Policy1"); + try { + zmsTest.putPolicy(mockDomRsrcCtx, "ReadOnlyDom1", "Policy1", auditRef, policy1); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains("Read-Only")); + } + + Role role1 = createRoleObject("ReadOnlyDom1", "Role1", null, + "user.joe", "user.jane"); + try { + zmsTest.putRole(mockDomRsrcCtx, "ReadOnlyDom1", "Role1", auditRef, role1); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains("Read-Only")); + } + + ServiceIdentity service1 = createServiceObject("ReadOnlyDom1", + "Service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + + try { + zmsTest.putServiceIdentity(mockDomRsrcCtx, "ReadOnlyDom1", "Service1", auditRef, service1); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains("Read-Only")); + } + + // now make sure we can read our sys.auth zms service + + ServiceIdentity serviceRes = zmsTest.getServiceIdentity(mockDomRsrcCtx, "sys.auth", "zms"); + assertNotNull(serviceRes); + assertEquals(serviceRes.getName(), "sys.auth.zms"); + + System.clearProperty(ZMSConsts.ZMS_PROP_READ_ONLY_MODE); + } + + @Test + public void testGetSignedDomainsResult() { + GetSignedDomainsResult object = new GetSignedDomainsResult(null); + assertFalse(object.isAsync()); + + try { + object.done(101); + } catch (WebApplicationException ex) { + } + } + + @Test + public void testResourceContext() { + + ZMSImpl.RsrcCtxWrapper ctx = (ZMSImpl.RsrcCtxWrapper) zms.newResourceContext(mockServletRequest, mockServletResponse); + assertNotNull(ctx); + assertNotNull(ctx.context()); + assertNull(ctx.principal()); + assertEquals(ctx.request(), mockServletRequest); + assertEquals(ctx.response(), mockServletResponse); + + try { + com.yahoo.athenz.common.server.rest.ResourceException restExc = new com.yahoo.athenz.common.server.rest.ResourceException(401, "failed struct"); + ctx.throwZmsException(restExc); + fail(); + } catch (ResourceException ex) { + assertEquals(401, ex.getCode()); + assertEquals( ((ResourceError) ex.data).message, "failed struct"); + } + } + + @Test + public void testEqualToOrPrefixedBy() { + assertTrue(zms.equalToOrPrefixedBy("pattern", "pattern")); + assertTrue(zms.equalToOrPrefixedBy("pattern", "pattern.")); + assertTrue(zms.equalToOrPrefixedBy("pattern", "pattern.test")); + assertFalse(zms.equalToOrPrefixedBy("pattern", "pattern-test")); + assertFalse(zms.equalToOrPrefixedBy("pattern", "patterns.test")); + assertFalse(zms.equalToOrPrefixedBy("pattern", "apattern.test")); + } + + @Test + public void testMatchRoleNoRoles() { + assertFalse(zms.matchRole("domain", new ArrayList(), "role", null)); + } + + @Test + public void testMatchRoleNoRoleMatch() { + Role role = new Role().setName("domain:role.role1"); + ArrayList roles = new ArrayList<>(); + roles.add(role); + assertFalse(zms.matchRole("domain", new ArrayList(), "domain:role\\.role2.*", null)); + } + + @Test + public void testMatchRoleAuthRoleNoMatch() { + Role role = new Role().setName("domain:role.role1"); + ArrayList roles = new ArrayList<>(); + roles.add(role); + + ArrayList authRoles = new ArrayList<>(); + authRoles.add("role3"); + + assertFalse(zms.matchRole("domain", roles, "domain:role\\.role1.*", authRoles)); + } + + @Test + public void testMatchRole() { + Role role = new Role().setName("domain:role.role1"); + ArrayList roles = new ArrayList<>(); + roles.add(role); + + ArrayList authRoles = new ArrayList<>(); + authRoles.add("role1"); + + assertTrue(zms.matchRole("domain", roles, "domain:role\\.role.*", authRoles)); + } + + @Test + public void testGetYrnDomain() { + assertEquals(zms.yrnDomain("yrn:service:location:domain:entity"), "domain"); + } + + @Test + public void testServerInternalError() { + + RuntimeException ex = ZMSUtils.internalServerError("unit test", "tester"); + assertTrue(ex.getMessage().contains("{code: 500")); + } + + @Test + public void testGetSchema() { + Schema schema = zms.getRdlSchema(mockDomRsrcCtx); + assertNotNull(schema); + } + + @Test + public void testValidatePolicyAssertionsInValid() { + + // assertion missing domain name + + Assertion assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("resource1"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + List assertList = new ArrayList(); + assertList.add(assertion); + + try { + zms.validatePolicyAssertions(assertList, "unitTest"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + // assertion with empty domain name + + assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource(":resource1"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + assertList.clear(); + assertList.add(assertion); + + try { + zms.validatePolicyAssertions(assertList, "unitTest"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + // assertion with invalid domain name + + assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("domain name:resource1"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + assertList.clear(); + assertList.add(assertion); + + try { + zms.validatePolicyAssertions(assertList, "unitTest"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + } + + @Test + public void testValidatePolicyAssertionInValid() { + + // assertion missing domain name + + Assertion assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("resource1"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + try { + zms.validatePolicyAssertion(assertion, "unitTest"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + // assertion with empty domain name + + assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource(":resource1"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + try { + zms.validatePolicyAssertion(assertion, "unitTest"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + + // assertion with invalid domain name + + assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("domain name:resource1"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + try { + zms.validatePolicyAssertion(assertion, "unitTest"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + } + + @Test + public void testValidatePolicyAssertionsValid() { + + Assertion assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("domain1:resource1"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + List assertList = new ArrayList(); + assertList.add(assertion); + + assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:resource1"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + assertList.add(assertion); + + assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("domain1:"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + assertList.add(assertion); + + try { + zms.validatePolicyAssertions(assertList, "unitTest"); + } catch (Exception ex) { + fail(ex.getMessage()); + } + + // null should also be valid + + try { + zms.validatePolicyAssertions(null, "unitTest"); + } catch (Exception ex) { + fail(ex.getMessage()); + } + } + + @Test + public void testValidatePolicyAssertionValid() { + + Assertion assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("domain1:resource1"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + try { + zms.validatePolicyAssertion(assertion, "unitTest"); + } catch (Exception ex) { + fail(ex.getMessage()); + } + + assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:resource1"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + try { + zms.validatePolicyAssertion(assertion, "unitTest"); + } catch (Exception ex) { + fail(ex.getMessage()); + } + + assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("domain1:"); + assertion.setRole(ZMSUtils.roleResourceName("domain1", "role1")); + + try { + zms.validatePolicyAssertion(assertion, "unitTest"); + } catch (Exception ex) { + fail(ex.getMessage()); + } + } + + @Test + public void testSetupRoleListWithMembers() { + + String domainName = "setuprolelistwithmembers"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject(domainName, "Role1", null, "user.joe", + "user.jane"); + zms.putRole(mockDomRsrcCtx, domainName, "Role1", auditRef, role1); + + Role role2 = createRoleObject(domainName, "Role2", null, "user.doe", + "user.janie"); + zms.putRole(mockDomRsrcCtx, domainName, "Role2", auditRef, role2); + + Role role3 = createRoleObject(domainName, "Role3", "sys.auth", null, null); + zms.putRole(mockDomRsrcCtx, domainName, "Role3", auditRef, role3); + + AthenzDomain domain = zms.getAthenzDomain(domainName, false); + List roles = zms.setupRoleList(domain, Boolean.valueOf(true)); + assertEquals(4, roles.size()); // need to account for admin role + + boolean role1Check = false; + boolean role2Check = false; + boolean role3Check = false; + + for (Role role : roles) { + switch (role.getName()) { + case "setuprolelistwithmembers:role.role1": + assertTrue(role.getMembers().contains("user.joe")); + assertTrue(role.getMembers().contains("user.jane")); + assertEquals(role.getMembers().size(), 2); + assertNull(role.getTrust()); + assertNotNull(role.getModified()); + role1Check = true; + break; + case "setuprolelistwithmembers:role.role2": + assertTrue(role.getMembers().contains("user.doe")); + assertTrue(role.getMembers().contains("user.janie")); + assertEquals(role.getMembers().size(), 2); + assertNull(role.getTrust()); + assertNotNull(role.getModified()); + role2Check = true; + break; + case "setuprolelistwithmembers:role.role3": + assertEquals(role.getTrust(), "sys.auth"); + assertNull(role.getMembers()); + role3Check = true; + assertNotNull(role.getModified()); + break; + } + } + + assertTrue(role1Check); + assertTrue(role2Check); + assertTrue(role3Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testSetupRoleListWithOutMembers() { + + String domainName = "setuprolelistwithoutmembers"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject(domainName, "Role1", null, "user.joe", + "user.jane"); + zms.putRole(mockDomRsrcCtx, domainName, "Role1", auditRef, role1); + + Role role2 = createRoleObject(domainName, "Role2", null, "user.doe", + "user.janie"); + zms.putRole(mockDomRsrcCtx, domainName, "Role2", auditRef, role2); + + Role role3 = createRoleObject(domainName, "Role3", "sys.auth", null, null); + zms.putRole(mockDomRsrcCtx, domainName, "Role3", auditRef, role3); + + AthenzDomain domain = zms.getAthenzDomain(domainName, false); + List roles = zms.setupRoleList(domain, Boolean.valueOf(false)); + assertEquals(4, roles.size()); // need to account for admin role + + boolean role1Check = false; + boolean role2Check = false; + boolean role3Check = false; + + for (Role role : roles) { + switch (role.getName()) { + case "setuprolelistwithoutmembers:role.role1": + assertNull(role.getMembers()); + assertNull(role.getTrust()); + assertNotNull(role.getModified()); + role1Check = true; + break; + case "setuprolelistwithoutmembers:role.role2": + assertNull(role.getMembers()); + assertNull(role.getTrust()); + assertNotNull(role.getModified()); + role2Check = true; + break; + case "setuprolelistwithoutmembers:role.role3": + assertEquals(role.getTrust(), "sys.auth"); + assertNull(role.getMembers()); + role3Check = true; + assertNotNull(role.getModified()); + break; + } + } + + assertTrue(role1Check); + assertTrue(role2Check); + assertTrue(role3Check); + + // we'll do the same check this time passing null + // for the boolean flag instead of false + + roles = zms.setupRoleList(domain, null); + assertEquals(4, roles.size()); // need to account for admin role + + role1Check = false; + role2Check = false; + role3Check = false; + + for (Role role : roles) { + switch (role.getName()) { + case "setuprolelistwithoutmembers:role.role1": + assertNull(role.getMembers()); + assertNull(role.getTrust()); + assertNotNull(role.getModified()); + role1Check = true; + break; + case "setuprolelistwithoutmembers:role.role2": + assertNull(role.getMembers()); + assertNull(role.getTrust()); + assertNotNull(role.getModified()); + role2Check = true; + break; + case "setuprolelistwithoutmembers:role.role3": + assertEquals(role.getTrust(), "sys.auth"); + assertNull(role.getMembers()); + role3Check = true; + assertNotNull(role.getModified()); + break; + } + } + + assertTrue(role1Check); + assertTrue(role2Check); + assertTrue(role3Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetRoles() { + + String domainName = "getroles"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Role role1 = createRoleObject(domainName, "Role1", null, "user.joe", + "user.jane"); + zms.putRole(mockDomRsrcCtx, domainName, "Role1", auditRef, role1); + + Role role2 = createRoleObject(domainName, "Role2", null, "user.doe", + "user.janie"); + zms.putRole(mockDomRsrcCtx, domainName, "Role2", auditRef, role2); + + Role role3 = createRoleObject(domainName, "Role3", "sys.auth", null, null); + zms.putRole(mockDomRsrcCtx, domainName, "Role3", auditRef, role3); + + Roles roleList = zms.getRoles(mockDomRsrcCtx, domainName, Boolean.valueOf(true)); + List roles = roleList.getList(); + assertEquals(4, roles.size()); // need to account for admin role + + boolean role1Check = false; + boolean role2Check = false; + boolean role3Check = false; + + for (Role role : roles) { + switch (role.getName()) { + case "getroles:role.role1": + assertTrue(role.getMembers().contains("user.joe")); + assertTrue(role.getMembers().contains("user.jane")); + assertEquals(role.getMembers().size(), 2); + assertNull(role.getTrust()); + assertNotNull(role.getModified()); + role1Check = true; + break; + case "getroles:role.role2": + assertTrue(role.getMembers().contains("user.doe")); + assertTrue(role.getMembers().contains("user.janie")); + assertEquals(role.getMembers().size(), 2); + assertNull(role.getTrust()); + assertNotNull(role.getModified()); + role2Check = true; + break; + case "getroles:role.role3": + assertEquals(role.getTrust(), "sys.auth"); + assertNull(role.getMembers()); + role3Check = true; + assertNotNull(role.getModified()); + break; + } + } + + assertTrue(role1Check); + assertTrue(role2Check); + assertTrue(role3Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetRolesInvalidDomain() { + + final String domainName = "getrolesinvaliddomain"; + + try { + zms.getRoles(mockDomRsrcCtx, domainName, null); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testSetupPolicyListWithAssertions() { + + final String domainName = "setup-policy-with-assert"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject(domainName, "policy1"); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy1", auditRef, policy1); + + Policy policy2 = createPolicyObject(domainName, "policy2"); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy2", auditRef, policy2); + + AthenzDomain domain = zms.getAthenzDomain(domainName, false); + List policies = zms.setupPolicyList(domain, Boolean.valueOf(true)); + assertEquals(3, policies.size()); // need to account for admin policy + + boolean policy1Check = false; + boolean policy2Check = false; + + List testAssertions = null; + for (Policy policy : policies) { + switch (policy.getName()) { + case "setup-policy-with-assert:policy.policy1": + testAssertions = policy.getAssertions(); + assertEquals(testAssertions.size(), 1); + policy1Check = true; + break; + case "setup-policy-with-assert:policy.policy2": + testAssertions = policy.getAssertions(); + assertEquals(testAssertions.size(), 1); + policy2Check = true; + break; + } + } + + assertTrue(policy1Check); + assertTrue(policy2Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetPolicies() { + + final String domainName = "get-policies"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject(domainName, "policy1"); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy1", auditRef, policy1); + + Policy policy2 = createPolicyObject(domainName, "policy2"); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy2", auditRef, policy2); + + Policies policyList = zms.getPolicies(mockDomRsrcCtx, domainName, Boolean.valueOf(true)); + List policies = policyList.getList(); + assertEquals(3, policies.size()); // need to account for admin policy + + boolean policy1Check = false; + boolean policy2Check = false; + + List testAssertions = null; + for (Policy policy : policies) { + switch (policy.getName()) { + case "get-policies:policy.policy1": + testAssertions = policy.getAssertions(); + assertEquals(testAssertions.size(), 1); + policy1Check = true; + break; + case "get-policies:policy.policy2": + testAssertions = policy.getAssertions(); + assertEquals(testAssertions.size(), 1); + policy2Check = true; + break; + } + } + + assertTrue(policy1Check); + assertTrue(policy2Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetPoliciesInvalidDomain() { + + String domainName = "get-policies-invalid-domain"; + + try { + zms.getPolicies(mockDomRsrcCtx, domainName, Boolean.valueOf(true)); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testSetupPolicyListWithOutAssertions() { + + final String domainName = "setup-policy-without-assert"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy1 = createPolicyObject(domainName, "policy1"); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy1", auditRef, policy1); + + Policy policy2 = createPolicyObject(domainName, "policy2"); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy2", auditRef, policy2); + + AthenzDomain domain = zms.getAthenzDomain(domainName, false); + List policies = zms.setupPolicyList(domain, Boolean.valueOf(false)); + assertEquals(3, policies.size()); // need to account for admin policy + + boolean policy1Check = false; + boolean policy2Check = false; + + for (Policy policy : policies) { + switch (policy.getName()) { + case "setup-policy-without-assert:policy.policy1": + assertNull(policy.getAssertions()); + policy1Check = true; + break; + case "setup-policy-without-assert:policy.policy2": + assertNull(policy.getAssertions()); + policy2Check = true; + break; + } + } + + assertTrue(policy1Check); + assertTrue(policy2Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetServiceIdentities() { + + final String domainName = "get-services"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service1 = createServiceObject(domainName, + "service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + zms.putServiceIdentity(mockDomRsrcCtx, domainName, "service1", auditRef, service1); + + ServiceIdentity service2 = createServiceObject(domainName, + "service2", "http://localhost", "/usr/bin/java", "yahoo", + "users", "host2"); + zms.putServiceIdentity(mockDomRsrcCtx, domainName, "service2", auditRef, service2); + + ServiceIdentities serviceList = zms.getServiceIdentities(mockDomRsrcCtx, domainName, + Boolean.valueOf(true), Boolean.valueOf(true)); + List services = serviceList.getList(); + assertEquals(2, services.size()); + + boolean service1Check = false; + boolean service2Check = false; + + for (ServiceIdentity service : services) { + switch (service.getName()) { + case "get-services.service1": + assertEquals(service.getExecutable(), "/usr/bin/java"); + assertEquals(service.getUser(), "root"); + assertEquals(service.getPublicKeys().size(), 2); + assertEquals(service.getHosts().size(), 1); + assertEquals(service.getHosts().get(0), "host1"); + service1Check = true; + break; + case "get-services.service2": + assertEquals(service.getExecutable(), "/usr/bin/java"); + assertEquals(service.getUser(), "yahoo"); + assertEquals(service.getPublicKeys().size(), 2); + assertEquals(service.getHosts().size(), 1); + assertEquals(service.getHosts().get(0), "host2"); + service2Check = true; + break; + } + } + + assertTrue(service1Check); + assertTrue(service2Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetServiceIdentitiesInvalidDomain() { + + String domainName = "get-services-invalid-domain"; + + try { + zms.getServiceIdentities(mockDomRsrcCtx, domainName, + Boolean.valueOf(true), Boolean.valueOf(true)); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testSetupServiceListWithKeysHosts() { + + final String domainName = "setup-service-keys-hosts"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service1 = createServiceObject(domainName, + "service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + zms.putServiceIdentity(mockDomRsrcCtx, domainName, "service1", auditRef, service1); + + ServiceIdentity service2 = createServiceObject(domainName, + "service2", "http://localhost", "/usr/bin/java", "yahoo", + "users", "host2"); + zms.putServiceIdentity(mockDomRsrcCtx, domainName, "service2", auditRef, service2); + + AthenzDomain domain = zms.getAthenzDomain(domainName, false); + List services = zms.setupServiceIdentityList(domain, + Boolean.valueOf(true), Boolean.valueOf(true)); + assertEquals(2, services.size()); + + boolean service1Check = false; + boolean service2Check = false; + + for (ServiceIdentity service : services) { + switch (service.getName()) { + case "setup-service-keys-hosts.service1": + assertEquals(service.getExecutable(), "/usr/bin/java"); + assertEquals(service.getUser(), "root"); + assertEquals(service.getPublicKeys().size(), 2); + assertEquals(service.getHosts().size(), 1); + assertEquals(service.getHosts().get(0), "host1"); + service1Check = true; + break; + case "setup-service-keys-hosts.service2": + assertEquals(service.getExecutable(), "/usr/bin/java"); + assertEquals(service.getUser(), "yahoo"); + assertEquals(service.getPublicKeys().size(), 2); + assertEquals(service.getHosts().size(), 1); + assertEquals(service.getHosts().get(0), "host2"); + service2Check = true; + break; + } + } + + assertTrue(service1Check); + assertTrue(service2Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testSetupServiceListWithOutKeysHosts() { + + final String domainName = "setup-service-without-keys-hosts"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service1 = createServiceObject(domainName, + "service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + zms.putServiceIdentity(mockDomRsrcCtx, domainName, "service1", auditRef, service1); + + ServiceIdentity service2 = createServiceObject(domainName, + "service2", "http://localhost", "/usr/bin/java", "yahoo", + "users", "host2"); + zms.putServiceIdentity(mockDomRsrcCtx, domainName, "service2", auditRef, service2); + + AthenzDomain domain = zms.getAthenzDomain(domainName, false); + List services = zms.setupServiceIdentityList(domain, + Boolean.valueOf(false), Boolean.valueOf(false)); + assertEquals(2, services.size()); + + boolean service1Check = false; + boolean service2Check = false; + + for (ServiceIdentity service : services) { + switch (service.getName()) { + case "setup-service-without-keys-hosts.service1": + assertEquals(service.getExecutable(), "/usr/bin/java"); + assertEquals(service.getUser(), "root"); + assertNull(service.getPublicKeys()); + assertNull(service.getHosts()); + service1Check = true; + break; + case "setup-service-without-keys-hosts.service2": + assertEquals(service.getExecutable(), "/usr/bin/java"); + assertEquals(service.getUser(), "yahoo"); + assertNull(service.getPublicKeys()); + assertNull(service.getHosts()); + service2Check = true; + break; + } + } + + assertTrue(service1Check); + assertTrue(service2Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testSetupServiceListWithKeysOnly() { + + final String domainName = "setup-service-keys-only"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service1 = createServiceObject(domainName, + "service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + zms.putServiceIdentity(mockDomRsrcCtx, domainName, "service1", auditRef, service1); + + ServiceIdentity service2 = createServiceObject(domainName, + "service2", "http://localhost", "/usr/bin/java", "yahoo", + "users", "host2"); + zms.putServiceIdentity(mockDomRsrcCtx, domainName, "service2", auditRef, service2); + + AthenzDomain domain = zms.getAthenzDomain(domainName, false); + List services = zms.setupServiceIdentityList(domain, + Boolean.valueOf(true), Boolean.valueOf(false)); + assertEquals(2, services.size()); + + boolean service1Check = false; + boolean service2Check = false; + + for (ServiceIdentity service : services) { + switch (service.getName()) { + case "setup-service-keys-only.service1": + assertEquals(service.getExecutable(), "/usr/bin/java"); + assertEquals(service.getUser(), "root"); + assertEquals(service.getPublicKeys().size(), 2); + assertNull(service.getHosts()); + service1Check = true; + break; + case "setup-service-keys-only.service2": + assertEquals(service.getExecutable(), "/usr/bin/java"); + assertEquals(service.getUser(), "yahoo"); + assertEquals(service.getPublicKeys().size(), 2); + assertNull(service.getHosts()); + service2Check = true; + break; + } + } + + assertTrue(service1Check); + assertTrue(service2Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testSetupServiceListWithHostsOnly() { + + final String domainName = "setup-service-hosts-only"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + ServiceIdentity service1 = createServiceObject(domainName, + "service1", "http://localhost", "/usr/bin/java", "root", + "users", "host1"); + zms.putServiceIdentity(mockDomRsrcCtx, domainName, "service1", auditRef, service1); + + ServiceIdentity service2 = createServiceObject(domainName, + "service2", "http://localhost", "/usr/bin/java", "yahoo", + "users", "host2"); + zms.putServiceIdentity(mockDomRsrcCtx, domainName, "service2", auditRef, service2); + + AthenzDomain domain = zms.getAthenzDomain(domainName, false); + List services = zms.setupServiceIdentityList(domain, + Boolean.valueOf(false), Boolean.valueOf(true)); + assertEquals(2, services.size()); + + boolean service1Check = false; + boolean service2Check = false; + + for (ServiceIdentity service : services) { + switch (service.getName()) { + case "setup-service-hosts-only.service1": + assertEquals(service.getExecutable(), "/usr/bin/java"); + assertEquals(service.getUser(), "root"); + assertNull(service.getPublicKeys()); + assertEquals(service.getHosts().size(), 1); + assertEquals(service.getHosts().get(0), "host1"); + service1Check = true; + break; + case "setup-service-hosts-only.service2": + assertEquals(service.getExecutable(), "/usr/bin/java"); + assertEquals(service.getUser(), "yahoo"); + assertNull(service.getPublicKeys()); + assertEquals(service.getHosts().size(), 1); + assertEquals(service.getHosts().get(0), "host2"); + service2Check = true; + break; + } + } + + assertTrue(service1Check); + assertTrue(service2Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetAssertion() { + + final String domainName = "get-assertion"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = createPolicyObject(domainName, "policy1"); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy1", auditRef, policy); + + Policy policyRes = zms.getPolicy(mockDomRsrcCtx, domainName, "policy1"); + Long assertionId = policyRes.getAssertions().get(0).getId(); + + Assertion assertion = zms.getAssertion(mockDomRsrcCtx, domainName, "policy1", assertionId); + assertNotNull(assertion); + assertEquals(assertion.getAction(), "*"); + assertEquals(assertion.getResource(), domainName + ":*"); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetAssertionMultiple() { + + final String domainName = "get-assertion-multiple"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = createPolicyObject(domainName, "policy1"); + Assertion assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource(domainName + ":resource"); + assertion.setRole(ZMSUtils.roleResourceName(domainName, "admin")); + policy.getAssertions().add(assertion); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy1", auditRef, policy); + + Policy policyRes = zms.getPolicy(mockDomRsrcCtx, domainName, "policy1"); + List testAssertions = new ArrayList<>(); + + Long assertionId = policyRes.getAssertions().get(0).getId(); + Assertion testAssertion = zms.getAssertion(mockDomRsrcCtx, domainName, "policy1", assertionId); + assertNotNull(testAssertion); + testAssertions.add(testAssertion); + + assertionId = policyRes.getAssertions().get(1).getId(); + testAssertion = zms.getAssertion(mockDomRsrcCtx, domainName, "policy1", assertionId); + assertNotNull(testAssertion); + testAssertions.add(testAssertion); + + boolean assert1Check = false; + boolean assert2Check = false; + for (Assertion testAssert : testAssertions) { + switch (testAssert.getAction()) { + case "*": + assertEquals(testAssert.getResource(), domainName + ":*"); + assert1Check = true; + break; + case "update": + assertEquals(testAssert.getResource(), domainName + ":resource"); + assert2Check = true; + break; + } + } + assertTrue(assert1Check); + assertTrue(assert2Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetAssertionUnknownId() { + + final String domainName = "get-assertion-invalid"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = createPolicyObject(domainName, "policy1"); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy1", auditRef, policy); + + try { + zms.getAssertion(mockDomRsrcCtx, domainName, "policy1", Long.valueOf(1)); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testPutAssertion() { + + final String domainName = "put-assertion"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = createPolicyObject(domainName, "policy1"); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy1", auditRef, policy); + + Assertion assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource(domainName + ":resource"); + assertion.setRole(ZMSUtils.roleResourceName(domainName, "admin")); + + // add the assertion + + assertion = zms.putAssertion(mockDomRsrcCtx, domainName, "policy1", auditRef, assertion); + + // verity that the return assertion object has the id set + + assertNotNull(assertion.getId()); + + // validate that both assertions exist + + Policy policyRes = zms.getPolicy(mockDomRsrcCtx, domainName, "policy1"); + + boolean assert1Check = false; + boolean assert2Check = false; + for (Assertion testAssert : policyRes.getAssertions()) { + switch (testAssert.getAction()) { + case "*": + assertEquals(testAssert.getResource(), domainName + ":*"); + assert1Check = true; + break; + case "update": + assertEquals(testAssert.getResource(), domainName + ":resource"); + assert2Check = true; + break; + } + } + assertTrue(assert1Check); + assertTrue(assert2Check); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testPutAssertionAdminReject() { + + final String domainName = "put-assertion-admin"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Assertion assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource(domainName + ":resource"); + assertion.setRole(ZMSUtils.roleResourceName(domainName, "admin")); + + try { + zms.putAssertion(mockDomRsrcCtx, domainName, "admin", auditRef, assertion); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getMessage(), ex.getMessage().contains("admin policy cannot be modified")); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testPutAssertionUnknownPolicy() { + + final String domainName = "put-assertion-unknown"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Assertion assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource(domainName + ":resource"); + assertion.setRole(ZMSUtils.roleResourceName(domainName, "admin")); + + // add the assertion which should fail due to unknown policy name + + try { + zms.putAssertion(mockDomRsrcCtx, domainName, "policy2", auditRef, assertion); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testDeleteAssertionSingle() { + + final String domainName = "delete-assertion-single"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = createPolicyObject(domainName, "policy1"); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy1", auditRef, policy); + + // now let's delete the assertion directly + + Policy policyRes = zms.getPolicy(mockDomRsrcCtx, domainName, "policy1"); + Long assertionId = policyRes.getAssertions().get(0).getId(); + + zms.deleteAssertion(mockDomRsrcCtx, domainName, "policy1", assertionId, auditRef); + + policyRes = zms.getPolicy(mockDomRsrcCtx, domainName, "policy1"); + assertEquals(policyRes.getAssertions().size(), 0); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testDeleteAssertionMultiple() { + + final String domainName = "delete-assertion-multiple"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = createPolicyObject(domainName, "policy1"); + Assertion assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource(domainName + ":resource"); + assertion.setRole(ZMSUtils.roleResourceName(domainName, "admin")); + policy.getAssertions().add(assertion); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy1", auditRef, policy); + + Policy policyRes = zms.getPolicy(mockDomRsrcCtx, domainName, "policy1"); + + // we are going to delete assertion at index 0 + + Long assertionId = policyRes.getAssertions().get(0).getId(); + zms.deleteAssertion(mockDomRsrcCtx, domainName, "policy1", assertionId, auditRef); + + // remember the assertion action for index 1 + + String action = policyRes.getAssertions().get(1).getAction(); + + // fetch the policy again and verify the action + + policyRes = zms.getPolicy(mockDomRsrcCtx, domainName, "policy1"); + assertEquals(policyRes.getAssertions().size(), 1); + assertEquals(policyRes.getAssertions().get(0).getAction(), action); + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testDeleteAssertionAdminReject() { + + final String domainName = "delete-assertion-admin"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + try { + zms.deleteAssertion(mockDomRsrcCtx, domainName, "admin", Long.valueOf(101), auditRef); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getMessage(), ex.getMessage().contains("admin policy cannot be modified")); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testDeleteAssertionUnknown() { + + final String domainName = "delete-assertion-unknown"; + TopLevelDomain dom1 = createTopLevelDomainObject(domainName, + "Test Domain1", "testOrg", adminUser); + zms.postTopLevelDomain(mockDomRsrcCtx, auditRef, dom1); + + Policy policy = createPolicyObject(domainName, "policy1"); + zms.putPolicy(mockDomRsrcCtx, domainName, "policy1", auditRef, policy); + + // delete the assertion which should fail due to unknown policy name + + try { + zms.deleteAssertion(mockDomRsrcCtx, domainName, "policy2", Long.valueOf(1), auditRef); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + // delete the assertion which should fail due to unknown assertion id + + try { + zms.deleteAssertion(mockDomRsrcCtx, domainName, "policy1", Long.valueOf(1), auditRef); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + zms.deleteTopLevelDomain(mockDomRsrcCtx, domainName, auditRef); + } + + @Test + public void testGetPolicyListWithoutAssertionId() { + + assertNull(zms.getPolicyListWithoutAssertionId(null)); + + List emptyList = new ArrayList<>(); + List result = zms.getPolicyListWithoutAssertionId(emptyList); + assertTrue(result.isEmpty()); + + final String domainName = "assertion-test"; + Policy policy = createPolicyObject(domainName, "policy1"); + Assertion assertion = new Assertion(); + assertion.setAction("update"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource(domainName + ":resource"); + assertion.setRole(ZMSUtils.roleResourceName(domainName, "admin")); + assertion.setId(Long.valueOf(101)); + policy.getAssertions().add(assertion); + + List policyList = new ArrayList<>(); + policyList.add(policy); + + result = zms.getPolicyListWithoutAssertionId(policyList); + assertEquals(result.size(), 1); + Assertion testAssertion = result.get(0).getAssertions().get(0); + assertNull(testAssertion.getId()); + assertEquals(assertion.getAction(), "update"); + assertEquals(assertion.getEffect(), AssertionEffect.ALLOW); + assertEquals(assertion.getResource(), domainName + ":resource"); + assertEquals(assertion.getRole(), ZMSUtils.roleResourceName(domainName, "admin")); + } + + @Test + public void testIsConsistentRoleName() { + + Role role = new Role(); + + // yrn behavior + + role.setName("domain1:role.role1"); + assertTrue(zms.isConsistentRoleName("domain1", "role1", role)); + + // local name behavior + + role.setName("role1"); + assertTrue(zms.isConsistentRoleName("domain1", "role1", role)); + assertEquals(role.getName(), "domain1:role.role1"); + + // inconsistent behavior + + role.setName("domain1:role.role1"); + assertFalse(zms.isConsistentRoleName("domain1", "role2", role)); + + role.setName("role1"); + assertFalse(zms.isConsistentRoleName("domain1", "role2", role)); + } + + @Test + public void testIsConsistentPolicyName() { + + Policy policy = new Policy(); + + // yrn behavior + + policy.setName("domain1:policy.policy1"); + assertTrue(zms.isConsistentPolicyName("domain1", "policy1", policy)); + + // local name behavior + + policy.setName("policy1"); + assertTrue(zms.isConsistentPolicyName("domain1", "policy1", policy)); + assertEquals(policy.getName(), "domain1:policy.policy1"); + + // inconsistent behavior + + policy.setName("domain1:policy.policy1"); + assertFalse(zms.isConsistentPolicyName("domain1", "policy2", policy)); + + policy.setName("policy1"); + assertFalse(zms.isConsistentPolicyName("domain1", "policy2", policy)); + } + + @Test + public void testGetDomainListNotNull() { + Authority userAuthority = new com.yahoo.athenz.common.server.debug.DebugUserAuthority(); + String userId = "user1"; + Principal principal = SimplePrincipal.create("user", userId, userId + ":password", 0, userAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(userId); + ResourceContext rsrcCtx1 = createResourceContext(principal); + zms.getDomainList(rsrcCtx1, 100, null, null, 100, "account", 224, "roleMem1", "role1", null); + } + + @Test + public void testDeleteUserDomainNull() { + Authority userAuthority = new com.yahoo.athenz.common.server.debug.DebugUserAuthority(); + String userId = "user1"; + Principal principal = SimplePrincipal.create("user", userId, userId + ":password", 0, userAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(userId); + ResourceContext rsrcCtx1 = createResourceContext(principal); + try { + zms.deleteUserDomain(rsrcCtx1, null, null); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteDomainTemplateNull() { + Authority userAuthority = new com.yahoo.athenz.common.server.debug.DebugUserAuthority(); + String userId = "user1"; + Principal principal = SimplePrincipal.create("user", userId, userId + ":password", 0, userAuthority); + ((SimplePrincipal) principal).setUnsignedCreds(userId); + ResourceContext rsrcCtx1 = createResourceContext(principal); + try { + zms.deleteDomainTemplate(rsrcCtx1, "dom1", null, "zms"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testIsAllowedResourceLookForAllUsers() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal1 = principalAuthority.authenticate("v=U1;d=user;n=user1;s=signature", + "10.11.12.13", "GET", null); + try{ + zms.isAllowedResourceLookForAllUsers(principal1); + } catch(Exception ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteDomainTemplate() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal1 = principalAuthority.authenticate("v=U1;d=user;n=user1;s=signature", + "10.11.12.13", "GET", null); + ResourceContext rsrcCtx1 = createResourceContext(principal1); + try{ + zms.deleteDomainTemplate(rsrcCtx1, null, null, null); + } catch(Exception ex) { + assertTrue(true); + } + } + + @Test + public void testPutTenancyResourceGroupNull() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal1 = principalAuthority.authenticate("v=U1;d=user;n=user1;s=signature", + "10.11.12.13", "GET", null); + ResourceContext rsrcCtx1 = createResourceContext(principal1); + TenancyResourceGroup tenantResource = new TenancyResourceGroup(); + try{ + zms.putTenancyResourceGroup(rsrcCtx1, null, null, null, null, tenantResource); + } catch(Exception ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteTenancyResourceGroupNull() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal1 = principalAuthority.authenticate("v=U1;d=user;n=user1;s=signature", + "10.11.12.13", "GET", null); + ResourceContext rsrcCtx1 = createResourceContext(principal1); + try{ + zms.deleteTenancyResourceGroup(rsrcCtx1, null, null, null, null); + } catch(Exception ex) { + assertTrue(true); + } + } + + @Test + public void testPutTenantResourceGroupRolesNull() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal1 = principalAuthority.authenticate("v=U1;d=user;n=user1;s=signature", + "10.11.12.13", "GET", null); + ResourceContext rsrcCtx1 = createResourceContext(principal1); + TenantResourceGroupRoles tenantResource = new TenantResourceGroupRoles(); + try{ + zms.putTenantResourceGroupRoles(rsrcCtx1, null, null, null, null, null, tenantResource); + } catch(Exception ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteTenantResourceGroupRolesNull() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal1 = principalAuthority.authenticate("v=U1;d=user;n=user1;s=signature", + "10.11.12.13", "GET", null); + ResourceContext rsrcCtx1 = createResourceContext(principal1); + try{ + zms.deleteTenantResourceGroupRoles(rsrcCtx1, null, null, null, null, null); + } catch(Exception ex) { + assertTrue(true); + } + } + + @Test + public void testGetResourceAccessList() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal1 = principalAuthority.authenticate("v=U1;d=user;n=user1;s=signature", + "10.11.12.13", "GET", null); + ResourceContext rsrcCtx1 = createResourceContext(principal1); + try{ + zms.getResourceAccessList(rsrcCtx1, "principal", "UPDATE"); + } catch(Exception ex) { + assertTrue(true); + } + } + + @Test + public void testDeleteProviderResourceGroupRolesNull() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal1 = principalAuthority.authenticate("v=U1;d=user;n=user1;s=signature", + "10.11.12.13", "GET", null); + ResourceContext rsrcCtx1 = createResourceContext(principal1); + try{ + zms.deleteProviderResourceGroupRoles(rsrcCtx1, null, null, null, null, null); + } catch(Exception ex) { + assertTrue(true); + } + } + + @Test + public void testGetProviderClient() { + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + Principal principal1 = principalAuthority.authenticate("v=U1;d=user;n=user1;s=signature", + "10.11.12.13", "GET", null); + try{ + zms.setProviderClientClass(null); + zms.getProviderClient("localhost/zms", principal1); + } catch(Exception ex) { + assertTrue(true); + } + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSJettyContainerTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSJettyContainerTest.java new file mode 100644 index 00000000000..2a996840457 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSJettyContainerTest.java @@ -0,0 +1,425 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import static org.testng.Assert.*; + + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.RequestLog; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.Slf4jRequestLog; +import org.eclipse.jetty.server.handler.RequestLogHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.ThreadPool; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import com.yahoo.athenz.common.server.log.AthenzRequestLog; +import com.yahoo.athenz.common.server.log.AuditLogFactory; + +public class ZMSJettyContainerTest { + + @Mock ZMSHandler mockImpl; + + @BeforeClass + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @AfterMethod + public void cleanup() { + System.clearProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PATH); + System.clearProperty(ZMSConsts.ZMS_PROP_KEYSTORE_TYPE); + System.clearProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PASSWORD); + System.clearProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PATH); + System.clearProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_TYPE); + System.clearProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PASSWORD); + System.clearProperty(ZMSConsts.ZMS_PROP_KEYMANAGER_PASSWORD); + System.clearProperty(ZMSConsts.ZMS_PROP_EXCLUDED_CIPHER_SUITES); + System.clearProperty(ZMSConsts.ZMS_PROP_EXCLUDED_PROTOCOLS); + System.clearProperty(ZMSConsts.ZMS_PROP_IDLE_TIMEOUT); + System.clearProperty(ZMSConsts.ZMS_PROP_SEND_SERVER_VERSION); + System.clearProperty(ZMSConsts.ZMS_PROP_SEND_DATE_HEADER); + System.clearProperty(ZMSConsts.ZMS_PROP_OUTPUT_BUFFER_SIZE); + System.clearProperty(ZMSConsts.ZMS_PROP_REQUEST_HEADER_SIZE); + System.clearProperty(ZMSConsts.ZMS_PROP_RESPONSE_HEADER_SIZE); + System.clearProperty(ZMSConsts.ZMS_PROP_MAX_THREADS); + } + + @Test + public void testContainerThreadPool() { + + System.setProperty(ZMSConsts.ZMS_PROP_MAX_THREADS, "100"); + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + container.createServer(100); + + Server server = container.getServer(); + assertNotNull(server); + + ThreadPool threadPool = server.getThreadPool(); + assertNotNull(threadPool); + + // at this point we have no threads so the value is 0 + assertEquals(threadPool.getThreads(), 0); + assertEquals(threadPool.getIdleThreads(), 0); + } + + @Test + public void testRequestLogHandler() { + + System.setProperty(ZMSConsts.ZMS_PROP_ACCESS_LOG_RETAIN_DAYS, "3"); + System.setProperty(ZMSConsts.ZMS_PROP_MAX_THREADS, "100"); + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + container.createServer(100); + + container.addRequestLogHandler("/tmp/zms_log"); + + // now retrieve the request log handler + + Handler[] handlers = container.getHandlers().getHandlers(); + RequestLogHandler logHandler = null; + for (Handler handler : handlers) { + if (handler instanceof RequestLogHandler) { + logHandler = (RequestLogHandler) handler; + break; + } + } + + assertNotNull(logHandler); + + RequestLog reqLog = logHandler.getRequestLog(); + assertNotNull(reqLog); + assertEquals(reqLog.getClass(), AthenzRequestLog.class); + } + + @Test + public void testSlf4jRequestLogHandler() { + + System.setProperty(ZMSConsts.ZMS_PROP_ACCESS_SLF4J_LOGGER, "AthenzAccessLogger"); + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + container.resource(ZMSResources.class); + container.delegate(ZMSHandler.class, mockImpl); + container.createServer(100); + + container.addRequestLogHandler("/tmp/zms_log"); + + // now retrieve the request log handler + + Handler[] handlers = container.getHandlers().getHandlers(); + RequestLogHandler logHandler = null; + for (Handler handler : handlers) { + if (handler instanceof RequestLogHandler) { + logHandler = (RequestLogHandler) handler; + break; + } + } + + assertNotNull(logHandler); + + RequestLog reqLog = logHandler.getRequestLog(); + assertNotNull(reqLog); + assertEquals(reqLog.getClass(), Slf4jRequestLog.class); + assertEquals(((Slf4jRequestLog) reqLog).getLoggerName(), "AthenzAccessLogger"); + System.clearProperty(ZMSConsts.ZMS_PROP_ACCESS_SLF4J_LOGGER); + } + + @Test + public void testAddServletHandlers() { + System.setProperty(ZMSConsts.ZMS_PROP_KEEP_ALIVE, "false"); + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + container.resource(ZMSResources.class); + container.delegate(ZMSHandler.class, mockImpl); + container.createServer(100); + container.addServletHandlers("/tmp", "localhost"); + } + + @Test + public void testPrimaryContext() { + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + container.resource(ZMSResources.class); + container.delegate(ZMSHandler.class, mockImpl); + container.createServer(100); + container.addServletHandlers("/tmp", "localhost"); + + Handler[] handlers = container.getHandlers().getHandlers(); + ServletContextHandler srvHandler = null; + for (Handler handler : handlers) { + if (handler instanceof ServletContextHandler) { + srvHandler = (ServletContextHandler) handler; + break; + } + } + + assertNotNull(srvHandler); + assertEquals(srvHandler.getContextPath(), "/"); + } + + @Test + public void testHttpConfigurationValidHttpsPort() { + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + container.createServer(100); + + System.setProperty(ZMSConsts.ZMS_PROP_SEND_SERVER_VERSION, "true"); + System.setProperty(ZMSConsts.ZMS_PROP_SEND_DATE_HEADER, "false"); + System.setProperty(ZMSConsts.ZMS_PROP_OUTPUT_BUFFER_SIZE, "128"); + System.setProperty(ZMSConsts.ZMS_PROP_REQUEST_HEADER_SIZE, "256"); + System.setProperty(ZMSConsts.ZMS_PROP_RESPONSE_HEADER_SIZE, "512"); + + int httpsPort = 443; + + HttpConfiguration httpConfig = container.newHttpConfiguration(httpsPort); + assertNotNull(httpConfig); + + assertEquals(httpConfig.getOutputBufferSize(), 128); + assertFalse(httpConfig.getSendDateHeader()); + assertTrue(httpConfig.getSendServerVersion()); + assertEquals(httpConfig.getRequestHeaderSize(), 256); + assertEquals(httpConfig.getResponseHeaderSize(), 512); + assertEquals(httpConfig.getSecurePort(), httpsPort); + assertEquals(httpConfig.getSecureScheme(), "https"); + } + + @Test + public void testHttpConfigurationNoHttpsPort() { + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + container.createServer(100); + + System.setProperty(ZMSConsts.ZMS_PROP_SEND_SERVER_VERSION, "false"); + System.setProperty(ZMSConsts.ZMS_PROP_SEND_DATE_HEADER, "true"); + System.setProperty(ZMSConsts.ZMS_PROP_OUTPUT_BUFFER_SIZE, "64"); + System.setProperty(ZMSConsts.ZMS_PROP_REQUEST_HEADER_SIZE, "128"); + System.setProperty(ZMSConsts.ZMS_PROP_RESPONSE_HEADER_SIZE, "256"); + + HttpConfiguration httpConfig = container.newHttpConfiguration(0); + assertNotNull(httpConfig); + + assertEquals(httpConfig.getOutputBufferSize(), 64); + assertTrue(httpConfig.getSendDateHeader()); + assertFalse(httpConfig.getSendServerVersion()); + assertEquals(httpConfig.getRequestHeaderSize(), 128); + assertEquals(httpConfig.getResponseHeaderSize(), 256); + assertEquals(httpConfig.getSecurePort(), 0); + + // it defaults to https even if we have no value specified + assertEquals(httpConfig.getSecureScheme(), "https"); + } + + @Test + public void testHttpConnectorsBoth() { + + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PATH, "/tmp/keystore"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_TYPE, "PKCS12"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PATH, "/tmp/truststore"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYMANAGER_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_IDLE_TIMEOUT, "10001"); + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + container.createServer(100); + + HttpConfiguration httpConfig = container.newHttpConfiguration(8082); + container.addHTTPConnectors(httpConfig, 8081, 8082); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 2); + + assertEquals(connectors[0].getIdleTimeout(), 10001); + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + + System.out.println("Connectors: " + connectors[1].getProtocols().toString()); + assertTrue(connectors[1].getProtocols().contains("http/1.1")); + assertTrue(connectors[1].getProtocols().contains("ssl")); + } + + @Test + public void testHttpConnectorsHttpsOnly() { + + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PATH, "file:///tmp/keystore"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_TYPE, "PKCS12"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PATH, "file:///tmp/truststore"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYMANAGER_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_IDLE_TIMEOUT, "10001"); + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + container.createServer(100); + + HttpConfiguration httpConfig = container.newHttpConfiguration(8082); + container.addHTTPConnectors(httpConfig, 0, 8082); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 1); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + assertTrue(connectors[0].getProtocols().contains("ssl")); + } + + @Test + public void testHttpConnectorsHttpOnly() { + + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PATH, "file:///tmp/keystore"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_TYPE, "PKCS12"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PATH, "file:///tmp/truststore"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYMANAGER_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_IDLE_TIMEOUT, "10001"); + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + container.createServer(100); + + HttpConfiguration httpConfig = container.newHttpConfiguration(0); + container.addHTTPConnectors(httpConfig, 8081, 0); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 1); + + assertEquals(connectors[0].getIdleTimeout(), 10001); + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + assertFalse(connectors[0].getProtocols().contains("ssl")); + } + + @Test + public void testFilter() { + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + container.resource(ZMSResources.class); + container.delegate(ZMSHandler.class, mockImpl); + container.createServer(100); + container.addServletHandlers("/tmp", "localhost"); + + Handler[] handlers = container.getHandlers().getHandlers(); + ServletContextHandler srvHandler = null; + for (Handler handler : handlers) { + if (handler instanceof ServletContextHandler) { + srvHandler = (ServletContextHandler) handler; + break; + } + } + assertNotNull(srvHandler); + } + + @Test + public void testCreateSSLContextObject() { + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PATH, "file:///tmp/keystore"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_TYPE, "PKCS12"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PATH, "file:///tmp/truststore"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYMANAGER_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_EXCLUDED_CIPHER_SUITES, ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_CIPHER_SUITES); + System.setProperty(ZMSConsts.ZMS_PROP_EXCLUDED_PROTOCOLS, ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_PROTOCOLS); + + SslContextFactory sslContextFactory = container.createSSLContextObject(); + assertNotNull(sslContextFactory); + assertEquals(sslContextFactory.getKeyStorePath(), "file:///tmp/keystore"); + assertEquals(sslContextFactory.getKeyStoreType(), "PKCS12"); + assertEquals(sslContextFactory.getTrustStoreResource().toString(), "file:///tmp/truststore"); + assertEquals(sslContextFactory.getTrustStoreType(), "PKCS12"); + assertEquals(sslContextFactory.getExcludeCipherSuites(), ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_CIPHER_SUITES.split(",")); + assertEquals(sslContextFactory.getExcludeProtocols(), ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_PROTOCOLS.split(",")); + } + + @Test + public void testCreateSSLContextObjectNoValues() { + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + SslContextFactory sslContextFactory = container.createSSLContextObject(); + + assertNotNull(sslContextFactory); + assertNull(sslContextFactory.getKeyStoreResource()); + // store type always defaults to PKCS12 + assertEquals(sslContextFactory.getKeyStoreType(), "PKCS12"); + assertNull(sslContextFactory.getTrustStoreResource()); + // store type always defaults to PKCS12 + assertEquals(sslContextFactory.getTrustStoreType(), "PKCS12"); + } + + @Test + public void testCreateSSLContextObjectNoKeyStore() { + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PATH, "file:///tmp/truststore"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + System.setProperty(ZMSConsts.ZMS_PROP_TRUSTSTORE_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYMANAGER_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_EXCLUDED_CIPHER_SUITES, ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_CIPHER_SUITES); + System.setProperty(ZMSConsts.ZMS_PROP_EXCLUDED_PROTOCOLS, ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_PROTOCOLS); + + SslContextFactory sslContextFactory = container.createSSLContextObject(); + assertNotNull(sslContextFactory); + assertNull(sslContextFactory.getKeyStoreResource()); + // store type always defaults to PKCS12 + assertEquals(sslContextFactory.getKeyStoreType(), "PKCS12"); + assertEquals(sslContextFactory.getTrustStoreResource().toString(), "file:///tmp/truststore"); + assertEquals(sslContextFactory.getTrustStoreType(), "PKCS12"); + assertEquals(sslContextFactory.getExcludeCipherSuites(), ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_CIPHER_SUITES.split(",")); + assertEquals(sslContextFactory.getExcludeProtocols(), ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_PROTOCOLS.split(",")); + } + + @Test + public void testCreateSSLContextObjectNoTrustStore() { + + ZMSJettyContainer container = new ZMSJettyContainer(AuditLogFactory.getLogger()); + + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PATH, "file:///tmp/keystore"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_TYPE, "PKCS12"); + System.setProperty(ZMSConsts.ZMS_PROP_KEYSTORE_PASSWORD, "pass123"); + System.setProperty(ZMSConsts.ZMS_PROP_EXCLUDED_CIPHER_SUITES, ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_CIPHER_SUITES); + System.setProperty(ZMSConsts.ZMS_PROP_EXCLUDED_PROTOCOLS, ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_PROTOCOLS); + + SslContextFactory sslContextFactory = container.createSSLContextObject(); + assertNotNull(sslContextFactory); + assertEquals(sslContextFactory.getKeyStorePath(), "file:///tmp/keystore"); + assertEquals(sslContextFactory.getKeyStoreType(), "PKCS12"); + assertNull(sslContextFactory.getTrustStore()); + // store type always defaults to PKCS12 + assertEquals(sslContextFactory.getTrustStoreType(), "PKCS12"); + assertEquals(sslContextFactory.getExcludeCipherSuites(), ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_CIPHER_SUITES.split(",")); + assertEquals(sslContextFactory.getExcludeProtocols(), ZMSJettyContainer.ZMS_DEFAULT_EXCLUDED_PROTOCOLS.split(",")); + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSServerImplTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSServerImplTest.java new file mode 100644 index 00000000000..a2b57ecaac6 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSServerImplTest.java @@ -0,0 +1,112 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.common.metrics.MetricFactory; +import com.yahoo.athenz.common.server.log.AuditLogFactory; +import com.yahoo.athenz.zms.ZMSServerImpl; +import com.yahoo.athenz.zms.pkey.PrivateKeyStoreFactory; +import com.yahoo.athenz.zms.pkey.file.FilePrivateKeyStoreFactory; + +import static org.testng.Assert.*; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class ZMSServerImplTest { + + @BeforeClass + public void setUp() throws Exception { + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_STORE_CLASS, "com.yahoo.athenz.zms.pkey.file.FilePrivateKeyStoreFactory"); + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY, "src/test/resources/zms_private.pem"); + System.setProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY, "src/test/resources/zms_public.pem"); + System.setProperty(ZMSConsts.ZMS_PROP_DOMAIN_ADMIN, "user.testadminuser"); + } + + @Test + public void testDebugAuthorities() throws Exception { + + Authority principalAuthority = new com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority(); + principalAuthority.initialize(); + + Authority roleAuthority = new com.yahoo.athenz.common.server.debug.DebugRoleAuthority(); + roleAuthority.initialize(); + + com.yahoo.athenz.common.server.rest.Http.AuthorityList authList = new com.yahoo.athenz.common.server.rest.Http.AuthorityList(); + authList.add(principalAuthority); + authList.add(roleAuthority); + + String policyStoreContext = "."; + + MetricFactory debugMetricFactory = new com.yahoo.athenz.common.metrics.impl.NoOpMetricFactory(); + PrivateKeyStoreFactory privateKeyFactory = new FilePrivateKeyStoreFactory(); + ZMSServerImpl core = new ZMSServerImpl("localhost", policyStoreContext, + privateKeyFactory, debugMetricFactory, AuditLogFactory.getLogger(), + null, authList); + assertNotNull(core); + + assertNotNull(core.getAuthorizer()); + assertNotNull(core.getInstance()); + } + + @Test + public void testRealAuthorities() throws Exception { + + Authority principalAuthority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + Authority roleAuthority = new com.yahoo.athenz.auth.impl.RoleAuthority(); + + String policyStoreContext = "."; + + com.yahoo.athenz.common.server.rest.Http.AuthorityList authList = new com.yahoo.athenz.common.server.rest.Http.AuthorityList(); + authList.add(principalAuthority); + authList.add(roleAuthority); + + MetricFactory debugMetricFactory = new com.yahoo.athenz.common.metrics.impl.NoOpMetricFactory(); + PrivateKeyStoreFactory privateKeyFactory = new FilePrivateKeyStoreFactory(); + ZMSServerImpl core = new ZMSServerImpl("localhost", policyStoreContext, + privateKeyFactory, debugMetricFactory, AuditLogFactory.getLogger(), + null, authList); + assertNotNull(core); + + assertNotNull(core.getAuthorizer()); + assertNotNull(core.getInstance()); + } + + @Test + public void testNullContext() throws Exception { + + Authority principalAuthority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + Authority roleAuthority = new com.yahoo.athenz.auth.impl.RoleAuthority(); + + String policyStoreContext = null; + + com.yahoo.athenz.common.server.rest.Http.AuthorityList authList = new com.yahoo.athenz.common.server.rest.Http.AuthorityList(); + authList.add(principalAuthority); + authList.add(roleAuthority); + + MetricFactory debugMetricFactory = new com.yahoo.athenz.common.metrics.impl.NoOpMetricFactory(); + PrivateKeyStoreFactory privateKeyFactory = new FilePrivateKeyStoreFactory(); + ZMSServerImpl core = new ZMSServerImpl("localhost", policyStoreContext, + privateKeyFactory, debugMetricFactory, AuditLogFactory.getLogger(), + null, authList); + assertNotNull(core); + + assertNotNull(core.getAuthorizer()); + assertNotNull(core.getInstance()); + } +} + diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSTest.java new file mode 100644 index 00000000000..d5d192ab578 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/ZMSTest.java @@ -0,0 +1,192 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms; + +import static org.testng.Assert.*; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class ZMSTest { + + @BeforeClass + public void setUp() throws Exception { + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_STORE_CLASS, "com.yahoo.athenz.zms.pkey.file.FilePrivateKeyStoreFactory"); + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY, "src/test/resources/zms_private.pem"); + System.setProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY, "src/test/resources/zms_public.pem"); + System.setProperty(ZMSConsts.ZMS_PROP_DOMAIN_ADMIN, "user.testadminuser"); + } + + @AfterMethod + public void cleanup() { + System.clearProperty(ZMSConsts.ZMS_PROP_HOME); + System.clearProperty(ZMSConsts.ZMS_PROP_HOSTNAME); + System.clearProperty(ZMSConsts.ZMS_PROP_HTTP_PORT); + System.clearProperty(ZMSConsts.ZMS_PROP_HTTPS_PORT); + } + + @Test + public void testGetAuditLogger() { + System.setProperty(ZMSConsts.ZMS_PROP_AUDIT_LOGGER_CLASS, "zmsclass"); + System.setProperty(ZMSConsts.ZMS_PROP_AUDIT_LOGGER_CLASS_PARAM, "zmsparam"); + ZMS.getAuditLogger(); + } + + @Test + public void testGetServerHostNamePropertySet() { + System.setProperty(ZMSConsts.ZMS_PROP_HOSTNAME, "MyTestHost"); + assertEquals(ZMS.getServerHostName(), "MyTestHost"); + } + + @Test + public void testGetServerHostNameNoProperty() { + assertNotNull(ZMS.getServerHostName()); + } + + @Test + public void initContainerValidPorts() { + + System.setProperty(ZMSConsts.ZMS_PROP_HOME, "/tmp/zms_server"); + System.setProperty(ZMSConsts.ZMS_PROP_HTTP_PORT, "4080"); + System.setProperty(ZMSConsts.ZMS_PROP_HTTPS_PORT, "4443"); + + ZMSJettyContainer container = ZMS.createJettyContainer(); + assertNotNull(container); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 2); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + + System.out.println("Connectors: " + connectors[1].getProtocols().toString()); + assertTrue(connectors[1].getProtocols().contains("http/1.1")); + assertTrue(connectors[1].getProtocols().contains("ssl")); + } + + @Test + public void initContainerOnlyHTTPSPort() { + + System.setProperty(ZMSConsts.ZMS_PROP_HOME, "/tmp/zms_server"); + System.setProperty(ZMSConsts.ZMS_PROP_HTTP_PORT, "0"); + System.setProperty(ZMSConsts.ZMS_PROP_HTTPS_PORT, "4443"); + System.setProperty("yahoo.zms.debug.user_authority", "true"); + + ZMSJettyContainer container = ZMS.createJettyContainer(); + assertNotNull(container); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 1); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + assertTrue(connectors[0].getProtocols().contains("ssl")); + } + + @Test + public void initContainerOnlyHTTPPort() { + + System.setProperty(ZMSConsts.ZMS_PROP_HOME, "/tmp/zms_server"); + System.setProperty(ZMSConsts.ZMS_PROP_HTTP_PORT, "4080"); + System.setProperty(ZMSConsts.ZMS_PROP_HTTPS_PORT, "0"); + + ZMSJettyContainer container = ZMS.createJettyContainer(); + assertNotNull(container); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 1); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + assertFalse(connectors[0].getProtocols().contains("ssl")); + } + + @Test + public void initContainerInvalidHTTPPort() { + + System.setProperty(ZMSConsts.ZMS_PROP_HOME, "/tmp/zms_server"); + System.setProperty(ZMSConsts.ZMS_PROP_HTTP_PORT, "-10"); + System.setProperty(ZMSConsts.ZMS_PROP_HTTPS_PORT, "4443"); + + ZMSJettyContainer container = ZMS.createJettyContainer(); + assertNotNull(container); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 2); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + + System.out.println("Connectors: " + connectors[1].getProtocols().toString()); + assertTrue(connectors[1].getProtocols().contains("http/1.1")); + assertTrue(connectors[1].getProtocols().contains("ssl")); + } + + @Test + public void initContainerInvalidHTTPSPort() { + + System.setProperty(ZMSConsts.ZMS_PROP_HOME, "/tmp/zms_server"); + System.setProperty(ZMSConsts.ZMS_PROP_HTTP_PORT, "4080"); + System.setProperty(ZMSConsts.ZMS_PROP_HTTPS_PORT, "-10"); + + ZMSJettyContainer container = ZMS.createJettyContainer(); + assertNotNull(container); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 1); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + assertFalse(connectors[0].getProtocols().contains("ssl")); + } + + @Test + public void testGetPortNumberDefault() { + assertEquals(ZMS.getPortNumber("NotExistantProperty", 4080), 4080); + } + + @Test + public void testGetPortNumberValid() { + System.setProperty(ZMSConsts.ZMS_PROP_HTTP_PORT, "4085"); + assertEquals(ZMS.getPortNumber(ZMSConsts.ZMS_PROP_HTTP_PORT, 4080), 4085); + } + + @Test + public void testGetPortNumberInvalidFormat() { + System.setProperty(ZMSConsts.ZMS_PROP_HTTP_PORT, "abc"); + assertEquals(ZMS.getPortNumber(ZMSConsts.ZMS_PROP_HTTP_PORT, 4080), 4080); + } + + @Test + public void testGetPortNumberOutOfRangeNegative() { + System.setProperty(ZMSConsts.ZMS_PROP_HTTP_PORT, "-1"); + assertEquals(ZMS.getPortNumber(ZMSConsts.ZMS_PROP_HTTP_PORT, 4080), 4080); + } + + @Test + public void testGetPortNumberOutOfRangePositive() { + System.setProperty(ZMSConsts.ZMS_PROP_HTTP_PORT, "65536"); + assertEquals(ZMS.getPortNumber(ZMSConsts.ZMS_PROP_HTTP_PORT, 4080), 4080); + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/config/AllowedOperationTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/config/AllowedOperationTest.java new file mode 100644 index 00000000000..71050d813ee --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/config/AllowedOperationTest.java @@ -0,0 +1,303 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.config; + +import static org.testng.Assert.*; + +import org.testng.annotations.*; + +import java.util.Set; +import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; + +public class AllowedOperationTest { + + @BeforeClass(alwaysRun=true) + public void setUp() throws Exception { + } + + @AfterClass(alwaysRun=true) + public void shutdown() { + } + + @Test + public void testSetName() { + String name = "AllowedOperationName"; + AllowedOperation op = new AllowedOperation(); + op.setName(name); + String gottenName = op.getName(); + assertEquals(gottenName, name); + } + + @SuppressWarnings("serial") + @Test + public void testSetItems() { + AllowedOperation op = new AllowedOperation(); + Set item = new HashSet() { + { + add("hoge"); + } + }; + Map> items = new HashMap>() { + { + put("key", item); + } + }; + op.setItems(items); + + Map> gottenItems = op.getItems(); + assertEquals(gottenItems, items); + } + + + @Test + public void testIsOperationAllowedOnNoItems() { + AllowedOperation op = new AllowedOperation(); + boolean ret = op.isOperationAllowedOn("opItemType", "opItemValue"); + assertTrue(ret); + } + + @Test + public void testIsOperationAllowedOnItemsIsEmpty() { + AllowedOperation op = new AllowedOperation(); + Map> items = new HashMap>(); + op.setItems(items); + boolean ret = op.isOperationAllowedOn("opItemType", "opItemValue"); + assertTrue(ret); + } + + @SuppressWarnings("serial") + @Test + public void testIsOperationAllowedOnOpItemTypeIsNull() { + AllowedOperation op = new AllowedOperation(); + Set item = new HashSet() { + { + add("hoge"); + } + }; + Map> items = new HashMap>() { + { + put("key", item); + } + }; + op.setItems(items); + boolean ret = op.isOperationAllowedOn(null, "opItemValue"); + assertFalse(ret); + } + + @SuppressWarnings("serial") + @Test + public void testIsOperationAllowedOnOpItemValueIsNull() { + AllowedOperation op = new AllowedOperation(); + Set item = new HashSet() { + { + add("hoge"); + } + }; + Map> items = new HashMap>() { + { + put("key", item); + } + }; + op.setItems(items); + boolean ret = op.isOperationAllowedOn("opItemType", null); + assertFalse(ret); + } + + @SuppressWarnings("serial") + @Test + public void testIsOperationAllowedOnOpItemTypeIsNotDefined() { + AllowedOperation op = new AllowedOperation(); + Set item = new HashSet() { + { + add("hoge"); + } + }; + Map> items = new HashMap>() { + { + put("key", item); + } + }; + op.setItems(items); + boolean ret = op.isOperationAllowedOn("opItemType", "opItemValue"); + assertFalse(ret); + } + + @SuppressWarnings("serial") + @Test + public void testIsOperationAllowedOnOpItemValueIsNotAllowed() { + AllowedOperation op = new AllowedOperation(); + Set item = new HashSet() { + { + add("hoge"); + } + }; + Map> items = new HashMap>() { + { + put("key", item); + } + }; + op.setItems(items); + boolean ret = op.isOperationAllowedOn("key", "opItemValue"); + assertFalse(ret); + } + + @SuppressWarnings("serial") + @Test + public void testIsOperationAllowedOnOpItemValueIsAllowed() { + AllowedOperation op = new AllowedOperation(); + Set item = new HashSet() { + { + add("hoge"); + } + }; + Map> items = new HashMap>() { + { + put("key", item); + } + }; + op.setItems(items); + boolean ret = op.isOperationAllowedOn("key", "hoge"); + assertTrue(ret); + } + + @Test + public void testEqualsSameObject() { + AllowedOperation op1 = new AllowedOperation(); + boolean ret = op1.equals(op1); + assertTrue(ret); + } + + @Test + public void testEqualsObjctIsNull() { + AllowedOperation op1 = new AllowedOperation(); + Object obj = null; + boolean ret = op1.equals(obj); + assertFalse(ret); + } + + @Test + public void testEqualsDifferentClass() { + AllowedOperation op1 = new AllowedOperation(); + String str = "fuga"; + boolean ret = op1.equals(str); + assertFalse(ret); + } + + @Test + public void testEqualsNameIsNull() { + AllowedOperation op1 = new AllowedOperation(); + AllowedOperation op2 = new AllowedOperation(); + op2.setName("AllowedOperation2"); + + boolean ret = op1.equals(op2); + assertFalse(ret); + } + + @Test + public void testEqualsBothNameIsNull() { + AllowedOperation op1 = new AllowedOperation(); + AllowedOperation op2 = new AllowedOperation(); + + boolean ret = op1.equals(op2); + assertTrue(ret); + } + + @Test + public void testEqualsSameName() { + AllowedOperation op1 = new AllowedOperation(); + op1.setName("AllowedOperation1"); + AllowedOperation op2 = new AllowedOperation(); + op2.setName("AllowedOperation1"); + boolean ret = op1.equals(op2); + assertTrue(ret); + } + + @Test + public void testEqualsDifferentName() { + AllowedOperation op1 = new AllowedOperation(); + op1.setName("AllowedOperation1"); + AllowedOperation op2 = new AllowedOperation(); + op2.setName("AllowedOperation2"); + boolean ret = op1.equals(op2); + assertFalse(ret); + } + + @SuppressWarnings("serial") + @Test + public void testEqualsSameHashCode() { + AllowedOperation op1 = new AllowedOperation(); + Set item1 = new HashSet() { + { + add("hoge"); + } + }; + Map> items1 = new HashMap>() { + { + put("key", item1); + } + }; + op1.setItems(items1); + + AllowedOperation op2 = new AllowedOperation(); + Set item2 = new HashSet() { + { + add("hoge"); + } + }; + Map> items2 = new HashMap>() { + { + put("key", item2); + } + }; + op2.setItems(items2); + + assertEquals(op1.hashCode(), op2.hashCode()); + } + + @SuppressWarnings("serial") + @Test + public void testEqualsDifferentHashCode() { + AllowedOperation op1 = new AllowedOperation(); + Set item1 = new HashSet() { + { + add("hoge"); + } + }; + Map> items1 = new HashMap>() { + { + put("key", item1); + } + }; + op1.setItems(items1); + + AllowedOperation op2 = new AllowedOperation(); + Set item2 = new HashSet() { + { + add("fuga"); + } + }; + Map> items2 = new HashMap>() { + { + put("key", item2); + } + }; + op2.setItems(items2); + + assertNotEquals(op1.hashCode(), op2.hashCode()); + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/config/AuthorizedServicesTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/config/AuthorizedServicesTest.java new file mode 100644 index 00000000000..775ea1ae6d3 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/config/AuthorizedServicesTest.java @@ -0,0 +1,50 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.config; + +import static org.testng.Assert.assertTrue; +import java.util.HashMap; + +import org.mockito.Mockito; +import org.testng.annotations.Test; + +public class AuthorizedServicesTest { + + @Test + public void testGetServices() { + AuthorizedServices authorizedService = new AuthorizedServices(); + authorizedService.getServices(); + } + + @Test + public void testSetTemplates() { + AuthorizedServices authorizedService = new AuthorizedServices(); + @SuppressWarnings("unchecked") + HashMap authorizedServiceHash = Mockito.mock(HashMap.class); + authorizedService.setTemplates(authorizedServiceHash); + } + + @Test + public void testName() { + AuthorizedServices authorizedService = new AuthorizedServices(); + try { + authorizedService.names(); + } catch (Exception ex) { + assertTrue(true); + } + } + +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/config/SolutionTemplatesTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/config/SolutionTemplatesTest.java new file mode 100644 index 00000000000..33f36d3de3a --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/config/SolutionTemplatesTest.java @@ -0,0 +1,31 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.config; + +import java.util.HashMap; +import org.testng.annotations.Test; +import com.yahoo.athenz.zms.Template; +import static org.testng.Assert.assertNull; + +public class SolutionTemplatesTest { + + @Test + public void testGetTemplates() { + SolutionTemplates solution = new SolutionTemplates(); + HashMap templates = solution.getTemplates(); + assertNull(templates); + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/pkey/PrivateKeyStoreTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/pkey/PrivateKeyStoreTest.java new file mode 100644 index 00000000000..25d75025205 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/pkey/PrivateKeyStoreTest.java @@ -0,0 +1,42 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.pkey; + +import static org.testng.Assert.*; + +import java.security.PrivateKey; + +import org.testng.annotations.Test; + +public class PrivateKeyStoreTest { + + public class PrivateKeyStoreInstance implements PrivateKeyStore { + + @Test + public void testGetPrivateKeyMulti() { + PrivateKeyStoreInstance keystore = new PrivateKeyStoreInstance(); + StringBuilder sb = new StringBuilder(); + PrivateKey key = keystore.getPrivateKey(sb); + assertNull(key); + } + + @Test + public void testGetPEMPublicKey() { + PrivateKeyStoreInstance keystore = new PrivateKeyStoreInstance(); + assertNull(keystore.getPEMPublicKey()); + } + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStoreTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStoreTest.java new file mode 100644 index 00000000000..0cdf34204ee --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/pkey/file/FilePrivateKeyStoreTest.java @@ -0,0 +1,138 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.pkey.file; + +import static org.testng.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.PrivateKey; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.yahoo.athenz.zms.ZMSConsts; +import com.yahoo.athenz.zms.pkey.PrivateKeyStore; +import com.yahoo.athenz.zms.pkey.file.FilePrivateKeyStore; +import com.yahoo.athenz.zms.pkey.file.FilePrivateKeyStoreFactory; + +public class FilePrivateKeyStoreTest { + + @BeforeClass + public void setUp() throws Exception { + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY_STORE_CLASS, "com.yahoo.athenz.zms.pkey.file.FilePrivateKeyStoreFactory"); + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY, "src/test/resources/zms_private.pem"); + System.setProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY, "src/test/resources/zms_public.pem"); + } + + @Test + public void testCreateStore() { + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + assertNotNull(store); + } + + @Test + public void testRetrievePublicKeyValid() { + + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + String saveProp = System.getProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY); + System.setProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY, "src/test/resources/zms_public.pem"); + + String pubKey = store.getPEMPublicKey(); + assertNotNull(pubKey); + + System.setProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY, saveProp); + } + + @Test + public void testRetrievePublicKeyInValid() { + + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + String saveProp = System.getProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY); + System.setProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY, "src/test/resources/invalid_zms_public.pem"); + + try { + store.getPEMPublicKey(); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + System.setProperty(ZMSConsts.ZMS_PROP_PUBLIC_KEY, saveProp); + } + + @Test + public void testRetrievePrivateKeyValid() { + + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + String saveProp = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY); + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY, "src/test/resources/zms_private.pem"); + + StringBuilder keyId = new StringBuilder(256); + PrivateKey privKey = store.getPrivateKey(keyId); + assertNotNull(privKey); + + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY, saveProp); + } + + @Test + public void testRetrievePrivateKeyInValid() { + + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + String saveProp = System.getProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY); + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY, "src/test/resources/invalid_zms_private.pem"); + + try { + StringBuilder keyId = new StringBuilder(256); + store.getPrivateKey(keyId); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + System.setProperty(ZMSConsts.ZMS_PROP_PRIVATE_KEY, saveProp); + } + + @Test + public void testGetStringNullStream() throws IOException { + + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + FilePrivateKeyStore store = (FilePrivateKeyStore) factory.create("localhost"); + assertNull(store.getString(null)); + } + + @Test + public void testGetString() throws IOException { + String str = "This is a Unit Test String"; + + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + FilePrivateKeyStore store = (FilePrivateKeyStore) factory.create("localhost"); + try (InputStream is = new ByteArrayInputStream(str.getBytes("UTF-8"))) { + String getStr = store.getString(is); + assertEquals(getStr, str); + } + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/store/file/FileConnectionTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/file/FileConnectionTest.java new file mode 100644 index 00000000000..2060379b255 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/file/FileConnectionTest.java @@ -0,0 +1,514 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.file; + +import static org.testng.Assert.*; +import java.io.File; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.AssertionEffect; +import com.yahoo.athenz.zms.Domain; +import com.yahoo.athenz.zms.Entity; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.PublicKeyEntry; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.ServiceIdentity; + +public class FileConnectionTest { + + @Test + public void testGetDomainModTimestamp() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + fileconnection.getDomainModTimestamp("DummyDomain1"); + } + } + + @Test + public void testUpdateDomain() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + Domain domMock = Mockito.mock(Domain.class); + Mockito.when(domMock.getName()).thenReturn("domain1"); + Mockito.when(fileconnection.getDomainStruct("domain1")).thenReturn(null); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testRemovePublicKeyEntry() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + assertFalse(fileconnection.removePublicKeyEntry(null, "12")); + } + } + + @Test + public void testLookupDomainByRole() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.lookupDomainByRole("member1", "role1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testValidatePrincipalDomain() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.validatePrincipalDomain("principal"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testUpdateDomainModTimestamp() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.updateDomainModTimestamp("DummyDomain1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testInsertDomainTemplate() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.insertDomainTemplate("DummyDomain1", "Template1", "param"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testDeleteDomainTemplate() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.deleteDomainTemplate("DummyDomain1", "Template1", "param"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testListEntities() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.listEntities("DummyDomain1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testListRoleMembers() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.listRoleMembers("DummyDomain1", "Role1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testInsertRoleMember() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.insertRoleMember("DummyDomain1", "Role1", "principal1", "audit1", "zmsjcltest"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testUpdatePolicy() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + Policy policy = new Policy(); + try { + fileconnection.updatePolicy("Domain1", policy); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testDeletePolicy() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.deletePolicy("Domain1", "policy1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testInsertAssertion() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + Assertion assertion = new Assertion(); + try { + fileconnection.insertAssertion("Domain1", "policy1", assertion); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testListAssertions() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.listAssertions("Domain1", "Policy1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testUpdateServiceIdentity() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + ServiceIdentity service1 = new ServiceIdentity(); + try { + fileconnection.updateServiceIdentity("Domain1", service1); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testDeleteServiceIdentity() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.deleteServiceIdentity("Domain1", "service1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testListPublicKeys() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.listPublicKeys("Domain1", "service1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testListServiceHosts() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.listPublicKeys("Domain1", "service1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testInsertServiceHost() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.insertServiceHost("Domain1", "service1", "athenz.zms.client.zms_url"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testDeleteServiceHost() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.deleteServiceHost("Domain1", "service1", "athenz.zms.client.zms_url"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testUpdateEntity() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + Entity entity1 = new Entity(); + try { + fileconnection.updateEntity("Domain1", entity1); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testDeleteEntity() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.deleteEntity("Domain1", "entity1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testDelete() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + fileconnection.delete("zms"); + } + } + + @Test + public void testListResourceAccess() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.listResourceAccess("principal1", "UPDATE", "UserDomain1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testUpdatePolicyModTimestamp() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.updatePolicyModTimestamp("domain1", "policy1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testUpdateRole() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + Role role = new Role(); + try { + fileconnection.updateRole("domain1", role); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testUpdateRoleModTimestamp() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.updateRoleModTimestamp("domain1", "role1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testDeleteRole() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.deleteRole("domain1", "role1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testListServiceHostsList() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.listServiceHosts("domain1", "service1"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testGetRoleObject() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + DomainStruct domain = new DomainStruct(); + fileconnection.getRoleObject(domain, "role1"); + } + } + + @Test + public void testGetPolicyObject() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + DomainStruct domain = new DomainStruct(); + fileconnection.getPolicyObject(domain, "role1"); + } + } + + @Test + public void testDeleteRoleMember() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.deleteRoleMember("domain1", "role1", "principal", "admin", "zmsjcltest"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testGetAssertion() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + long assertid = 33456; + try { + fileconnection.getAssertion("domain1", "policy1", assertid); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testDeleteAssertion() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + long assertid = 33456; + try { + fileconnection.deleteAssertion("domain1", "policy1", assertid); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testDeletePublicKeyEntry() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + try { + fileconnection.deletePublicKeyEntry("domain1", "service1", "223"); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testUpdatePublicKeyEntry() { + File file = new File("/home/athenz/"); + try (FileConnection fileconnection = new FileConnection(file)) { + PublicKeyEntry keyEntry = new PublicKeyEntry(); + try { + fileconnection.updatePublicKeyEntry("domain1", "service1", keyEntry); + } catch (Exception ex) { + assertTrue(true); + } + } + } + + @Test + public void testAssertionMatch() { + File file = new File("/home/athenz/"); + Assertion assertion1 = new Assertion(); + assertion1.setAction("UPDATE").setResource("resource").setRole("zmsRole"); + Assertion assertion2 = new Assertion(); + assertion2.setAction("UPDATE").setResource("resource").setRole("zmsRole"); + try (FileConnection fileconnection = new FileConnection(file)) { + assertTrue(fileconnection.assertionMatch(assertion1, assertion2)); + + Assertion assertion3 = new Assertion(); + assertion3.setAction("UPDATE").setResource("resource").setRole("zmsRole"); + Assertion assertion4 = new Assertion(); + assertion4.setAction("Delete").setResource("resource").setRole("zmsRole"); + assertFalse(fileconnection.assertionMatch(assertion3, assertion4)); + + Assertion assertion5 = new Assertion(); + assertion5.setAction("UPDATE").setResource("resource1").setRole("zmsRole"); + Assertion assertion6 = new Assertion(); + assertion6.setAction("UPDATE").setResource("resource2").setRole("zmsRole"); + assertFalse(fileconnection.assertionMatch(assertion5, assertion6)); + + Assertion assertion7 = new Assertion(); + assertion7.setAction("UPDATE").setResource("resource").setRole("zmsRole1"); + Assertion assertion8 = new Assertion(); + assertion8.setAction("UPDATE").setResource("resource").setRole("zmsRole2"); + assertFalse(fileconnection.assertionMatch(assertion7, assertion8)); + + Assertion assertion9 = new Assertion(); + AssertionEffect effect1 = AssertionEffect.ALLOW; + assertion9.setAction("UPDATE").setResource("resource").setRole("zmsRole").setEffect(effect1); + Assertion assertion10 = new Assertion(); + assertion10.setAction("UPDATE").setResource("resource").setRole("zmsRole").setEffect(effect1); + assertTrue(fileconnection.assertionMatch(assertion9, assertion10)); + + Assertion assertion11 = new Assertion(); + AssertionEffect effect2 = AssertionEffect.ALLOW; + AssertionEffect effect3 = AssertionEffect.DENY; + assertion11.setAction("UPDATE").setResource("resource").setRole("zmsRole").setEffect(effect2); + Assertion assertion12 = new Assertion(); + assertion12.setAction("UPDATE").setResource("resource").setRole("zmsRole").setEffect(effect3); + assertFalse(fileconnection.assertionMatch(assertion11, assertion12)); + } + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/store/file/FileObjectStoreTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/file/FileObjectStoreTest.java new file mode 100644 index 00000000000..afcaea0dee7 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/file/FileObjectStoreTest.java @@ -0,0 +1,49 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.file; + +import static org.testng.Assert.*; +import java.io.File; + +import org.testng.annotations.Test; + +public class FileObjectStoreTest { + @Test + public void testError() { + try { + FileObjectStore.error("Not Found"); + } catch (RuntimeException ex) { + assertTrue(true); + } + } + + @Test + public void TestFileObjectStore() { + File file = new File("/home/athenz/"); + File file2 = new File("/home/hoge.txt"); + try { + new FileObjectStore(file); + } catch (Exception ex) { + assertTrue(true); + } + + try { + new FileObjectStore(file2); + } catch (Exception ex) { + assertTrue(true); + } + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/DataSourceFactoryTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/DataSourceFactoryTest.java new file mode 100644 index 00000000000..cb729fac783 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/DataSourceFactoryTest.java @@ -0,0 +1,320 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.jdbc; + +import static org.testng.Assert.*; + +import java.io.PrintWriter; + +import org.apache.commons.pool2.impl.BaseObjectPoolConfig; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import com.yahoo.athenz.zms.MockConnectionFactory; +import com.yahoo.athenz.zms.store.jdbc.DataSourceFactory; +import com.yahoo.athenz.zms.store.jdbc.DataSourceFactory.SimpleDataSource; +import com.yahoo.athenz.zms.store.jdbc.PoolableDataSource; + + +public class DataSourceFactoryTest { + + static final String ZMS_DBPOOL_PROP1 = "athenz.zms.db_pool_test_prop"; + + @Test + public void testCrateDataSourceWithMysqlUrl() { + + PoolableDataSource src = DataSourceFactory.create("jdbc:mysql:localhost:3306/athenz"); + assertNotNull(src); + } + + @Test + public void testCrateDataSourceWithSQLiteUrl() { + + PoolableDataSource src = DataSourceFactory.create("jdbc:sqlite:localhost:3306/athenz"); + assertNotNull(src); + } + + @Test + public void testCreateDataSourceWithUnknownUrl() { + + try { + DataSourceFactory.create("jdbc:tdb:localhost:11080"); + fail(); + } catch (RuntimeException ex) { + assertTrue(true); + } + } + + @Test + public void testCreateDataSourceWithFactory() { + + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_TTL, "10000"); + + MockConnectionFactory connectionFactory = new MockConnectionFactory(); + PoolableDataSource src = DataSourceFactory.create(connectionFactory); + assertNotNull(src); + + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_TTL); + } + + @Test + public void testCreateDataSourceWithFactoryInvalidTTL() { + + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_TTL, "abc"); + + // we ignore the invalid ttl and everything else should work fine + + MockConnectionFactory connectionFactory = new MockConnectionFactory(); + PoolableDataSource src = DataSourceFactory.create(connectionFactory); + assertNotNull(src); + + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_TTL); + } + + @Test + public void testPoolConfigDefaultValues() { + + GenericObjectPoolConfig config = DataSourceFactory.setupPoolConfig(); + assertNotNull(config); + assertEquals(config.getMaxTotal(), GenericObjectPoolConfig.DEFAULT_MAX_TOTAL); + assertEquals(config.getMaxIdle(), GenericObjectPoolConfig.DEFAULT_MAX_IDLE); + assertEquals(config.getMinIdle(), GenericObjectPoolConfig.DEFAULT_MIN_IDLE); + assertEquals(config.getMaxWaitMillis(), GenericObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS); + assertEquals(config.getMinEvictableIdleTimeMillis(), BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS); + assertEquals(config.getTimeBetweenEvictionRunsMillis(), BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS); + assertTrue(config.getTestWhileIdle()); + assertTrue(config.getTestOnBorrow()); + } + + @Test + public void testPoolConfigSpecifiedValues() { + + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_TOTAL, "10"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_IDLE, "20"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MIN_IDLE, "30"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_WAIT, "40"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_TIMEOUT, "50"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_INTERVAL, "60"); + + GenericObjectPoolConfig config = DataSourceFactory.setupPoolConfig(); + assertNotNull(config); + assertEquals(config.getMaxTotal(), 10); + assertEquals(config.getMaxIdle(), 20); + assertEquals(config.getMinIdle(), 30); + assertEquals(config.getMaxWaitMillis(), 40); + assertEquals(config.getMinEvictableIdleTimeMillis(), 50); + assertEquals(config.getTimeBetweenEvictionRunsMillis(), 60); + assertTrue(config.getTestWhileIdle()); + assertTrue(config.getTestOnBorrow()); + + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_TOTAL); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_IDLE); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MIN_IDLE); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_WAIT); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_TIMEOUT); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_INTERVAL); + } + + @Test + public void testPoolConfigInvalidValues() { + + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_TOTAL, "a"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_IDLE, "b"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MIN_IDLE, "c"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_WAIT, "d"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_TIMEOUT, "e"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_INTERVAL, "f"); + + GenericObjectPoolConfig config = DataSourceFactory.setupPoolConfig(); + assertNotNull(config); + + assertEquals(config.getMaxTotal(), GenericObjectPoolConfig.DEFAULT_MAX_TOTAL); + assertEquals(config.getMaxIdle(), GenericObjectPoolConfig.DEFAULT_MAX_IDLE); + assertEquals(config.getMinIdle(), GenericObjectPoolConfig.DEFAULT_MIN_IDLE); + assertEquals(config.getMaxWaitMillis(), GenericObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS); + assertEquals(config.getMinEvictableIdleTimeMillis(), BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS); + assertEquals(config.getTimeBetweenEvictionRunsMillis(), BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS); + assertTrue(config.getTestWhileIdle()); + assertTrue(config.getTestOnBorrow()); + + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_TOTAL); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_IDLE); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MIN_IDLE); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_WAIT); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_TIMEOUT); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_INTERVAL); + } + + @Test + public void testPoolConfigZeroValues() { + + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_TOTAL, "0"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_IDLE, "0"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MIN_IDLE, "0"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_WAIT, "0"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_TIMEOUT, "0"); + System.setProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_INTERVAL, "0"); + + GenericObjectPoolConfig config = DataSourceFactory.setupPoolConfig(); + assertNotNull(config); + + // MaxTotal and MaxIdle are set to -1 if the value is 0 + assertEquals(config.getMaxTotal(), -1); + assertEquals(config.getMaxIdle(), -1); + assertEquals(config.getMinIdle(), 0); + assertEquals(config.getMaxWaitMillis(), 0); + assertEquals(config.getMinEvictableIdleTimeMillis(), 0); + assertEquals(config.getTimeBetweenEvictionRunsMillis(), 0); + assertTrue(config.getTestWhileIdle()); + assertTrue(config.getTestOnBorrow()); + + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_TOTAL); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_IDLE); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MIN_IDLE); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_MAX_WAIT); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_TIMEOUT); + System.clearProperty(DataSourceFactory.ZMS_PROP_DBPOOL_EVICT_IDLE_INTERVAL); + } + + @Test + public void testRetrieveConfigSettingLong() { + + System.setProperty(ZMS_DBPOOL_PROP1, "100"); + assertEquals(DataSourceFactory.retrieveConfigSetting(ZMS_DBPOOL_PROP1, 20L), 100L); + + System.setProperty(ZMS_DBPOOL_PROP1, "0"); + assertEquals(DataSourceFactory.retrieveConfigSetting(ZMS_DBPOOL_PROP1, 20L), 0); + + System.setProperty(ZMS_DBPOOL_PROP1, "-100"); + assertEquals(DataSourceFactory.retrieveConfigSetting(ZMS_DBPOOL_PROP1, 20L), -100L); + + System.clearProperty(ZMS_DBPOOL_PROP1); + } + + @Test + public void testRetrieveConfigSettingLongNull() { + System.clearProperty(ZMS_DBPOOL_PROP1); + assertEquals(DataSourceFactory.retrieveConfigSetting(ZMS_DBPOOL_PROP1, 25L), 25L); + } + + @Test + public void testRetrieveConfigSettingLongInvalid() { + System.setProperty(ZMS_DBPOOL_PROP1, "abc"); + assertEquals(DataSourceFactory.retrieveConfigSetting(ZMS_DBPOOL_PROP1, 20L), 20L); + System.clearProperty(ZMS_DBPOOL_PROP1); + } + + @Test + public void testRetrieveConfigSettingInt() { + + System.setProperty(ZMS_DBPOOL_PROP1, "100"); + assertEquals(DataSourceFactory.retrieveConfigSetting(ZMS_DBPOOL_PROP1, 20), 100); + + System.setProperty(ZMS_DBPOOL_PROP1, "0"); + assertEquals(DataSourceFactory.retrieveConfigSetting(ZMS_DBPOOL_PROP1, 20), 0); + + System.setProperty(ZMS_DBPOOL_PROP1, "-100"); + assertEquals(DataSourceFactory.retrieveConfigSetting(ZMS_DBPOOL_PROP1, 20), -100); + + System.clearProperty(ZMS_DBPOOL_PROP1); + } + + @Test + public void testRetrieveConfigSettingIntNull() { + System.clearProperty(ZMS_DBPOOL_PROP1); + assertEquals(DataSourceFactory.retrieveConfigSetting(ZMS_DBPOOL_PROP1, 25), 25); + } + + @Test + public void testRetrieveConfigSettingIntInvalid() { + System.setProperty(ZMS_DBPOOL_PROP1, "abc"); + assertEquals(DataSourceFactory.retrieveConfigSetting(ZMS_DBPOOL_PROP1, 20), 20); + System.clearProperty(ZMS_DBPOOL_PROP1); + } + + @Test + public void testGetConnection() { + SimpleDataSource dataSource = new SimpleDataSource("zmsclass", "localhost:3306/athenz"); + assertEquals(dataSource.getLoginTimeout(), 0); + } + + @Test + public void testSetLoginTimeout() { + SimpleDataSource dataSource = new SimpleDataSource("zmsclass", "localhost:3306/athenz"); + dataSource.setLoginTimeout(30); + } + + @Test + public void testSetLogWriter() { + SimpleDataSource dataSource = new SimpleDataSource("zmsclass", "localhost:3306/athenz"); + PrintWriter writer = Mockito.mock(PrintWriter.class); + dataSource.setLogWriter(writer); + } + + @Test + public void testGetLogWriter() { + SimpleDataSource dataSource = new SimpleDataSource("zmsclass", "localhost:3306/athenz"); + dataSource.getLogWriter(); + } + + @Test + public void testConnectionGetConnection() { + SimpleDataSource dataSource = new SimpleDataSource("zmsclass", "localhost:3306/athenz"); + try { + dataSource.getConnection(); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + try { + dataSource.getConnection("user1", "pass1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + + } + + @Test + public void testGetParentLogger() { + SimpleDataSource dataSource = new SimpleDataSource("zmsclass", "localhost:3306/athenz"); + try { + dataSource.getParentLogger(); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + } + + @Test + public void testIsWrapperFor() { + SimpleDataSource dataSource = new SimpleDataSource("zmsclass", "localhost:3306/athenz"); + assertFalse(dataSource.isWrapperFor(String.class)); + } + + @Test + public void testUnwrap() { + SimpleDataSource dataSource = new SimpleDataSource("zmsclass", "localhost:3306/athenz"); + try { + dataSource.unwrap(String.class); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/JDBCConnectionTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/JDBCConnectionTest.java new file mode 100644 index 00000000000..256366611f1 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/JDBCConnectionTest.java @@ -0,0 +1,5263 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.jdbc; + +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.AssertionEffect; +import com.yahoo.athenz.zms.Domain; +import com.yahoo.athenz.zms.DomainModified; +import com.yahoo.athenz.zms.DomainModifiedList; +import com.yahoo.athenz.zms.Entity; +import com.yahoo.athenz.zms.Membership; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.PublicKeyEntry; +import com.yahoo.athenz.zms.ResourceAccess; +import com.yahoo.athenz.zms.ResourceAccessList; +import com.yahoo.athenz.zms.ResourceException; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.RoleAuditLog; +import com.yahoo.athenz.zms.ServiceIdentity; +import com.yahoo.athenz.zms.ZMSConsts; +import com.yahoo.athenz.zms.store.AthenzDomain; +import com.yahoo.athenz.zms.store.jdbc.JDBCConnection; +import com.yahoo.athenz.zms.store.jdbc.JDBCObjectStore; +import com.yahoo.athenz.zms.store.jdbc.PoolableDataSource; +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Struct; +import com.yahoo.rdl.UUID; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.Matchers; + +import static org.mockito.Mockito.times; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import junit.framework.TestCase; + +public class JDBCConnectionTest extends TestCase { + + @Mock PoolableDataSource mockDataSrc; + @Mock Statement mockStmt; + @Mock PreparedStatement mockPrepStmt; + @Mock Connection mockConn; + @Mock ResultSet mockResultSet; + @Mock JDBCConnection mockJDBCConn; + + JDBCObjectStore strStore; + String domainData; + String domainChangedData; + + @BeforeClass + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + Mockito.doReturn(mockConn).when(mockDataSrc).getConnection(); + Mockito.doReturn(mockStmt).when(mockConn).createStatement(); + Mockito.doReturn(mockResultSet).when(mockStmt).executeQuery(Matchers.isA(String.class)); + Mockito.doReturn(true).when(mockStmt).execute(Matchers.isA(String.class)); + Mockito.doReturn(mockPrepStmt).when(mockConn).prepareStatement(Matchers.isA(String.class)); + Mockito.doReturn(mockResultSet).when(mockPrepStmt).executeQuery(); + } + + @Test + public void testGetDomain() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn("my-domain").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_NAME); + Mockito.doReturn(new java.sql.Timestamp(1454358916)).when(mockResultSet).getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED); + Mockito.doReturn(true).when(mockResultSet).getBoolean(ZMSConsts.DB_COLUMN_ENABLED); + Mockito.doReturn(false).when(mockResultSet).getBoolean(ZMSConsts.DB_COLUMN_AUDIT_ENABLED); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_DESCRIPTION); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_ORG); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_UUID); + Mockito.doReturn("12345").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_ACCOUNT); + Mockito.doReturn(1001).when(mockResultSet).getInt(ZMSConsts.DB_COLUMN_PRODUCT_ID); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Domain domain = jdbcConn.getDomain("my-domain"); + assertNotNull(domain); + assertEquals("my-domain", domain.getName()); + assertTrue(domain.getEnabled()); + assertFalse(domain.getAuditEnabled()); + assertNull(domain.getDescription()); + assertNull(domain.getOrg()); + assertNull(domain.getId()); + jdbcConn.close(); + } + + @Test + public void testGetDomainNotFound() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Domain domain = jdbcConn.getDomain("my-domain"); + assertNull(domain); + jdbcConn.close(); + } + + @Test + public void testGetDomainId() throws Exception { + + // first time success from mysql, second time failure so + // we can verify we get the value from our cache + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn(7).when(mockResultSet).getInt(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals(7, jdbcConn.getDomainId(mockConn, "my-domain")); + assertEquals(7, jdbcConn.getDomainId(mockConn, "my-domain")); + + jdbcConn.close(); + } + + @Test + public void testGetDomainIdException() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + try { + jdbcConn.getDomainId(mockConn, "my-domain"); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetRoleId() throws Exception { + + // first time success from mysql, second time failure so + // we can verify we get the value from our cache + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn(9).when(mockResultSet).getInt(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals(9, jdbcConn.getRoleId(mockConn, 7, "role1")); + assertEquals(9, jdbcConn.getRoleId(mockConn, 7, "role1")); + + jdbcConn.close(); + } + + @Test + public void testGetRoleIdException() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + try { + jdbcConn.getRoleId(mockConn, 3, "role1"); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetPrincipalId() throws Exception { + + // first time success from mysql, second time failure so + // we can verify we get the value from our cache + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn(7).when(mockResultSet).getInt(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals(7, jdbcConn.getPrincipalId(mockConn, "my-domain.user1")); + assertEquals(7, jdbcConn.getPrincipalId(mockConn, "my-domain.user1")); + + jdbcConn.close(); + } + + @Test + public void testGetPrincipalIdException() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + try { + jdbcConn.getPrincipalId(mockConn, "domain.user1"); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetLastInsertIdFailure() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.next()).thenReturn(false); + + assertEquals(0, jdbcConn.getLastInsertId(mockConn)); + jdbcConn.close(); + } + + @Test + public void testGetLastInsertIdException() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + try { + jdbcConn.getLastInsertId(mockConn); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetPolicyId() throws Exception { + + // first time success from mysql, second time failure so + // we can verify we get the value from our cache + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn(9).when(mockResultSet).getInt(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals(9, jdbcConn.getPolicyId(mockConn, 7, "policy1")); + assertEquals(9, jdbcConn.getPolicyId(mockConn, 7, "policy1")); + + jdbcConn.close(); + } + + @Test + public void testGetPolicyIdException() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + try { + jdbcConn.getPolicyId(mockConn, 3, "policy1"); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetServiceId() throws Exception { + + // first time success from mysql, second time failure so + // we can verify we get the value from our cache + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn(9).when(mockResultSet).getInt(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals(9, jdbcConn.getServiceId(mockConn, 7, "service1")); + assertEquals(9, jdbcConn.getServiceId(mockConn, 7, "service1")); + + jdbcConn.close(); + } + + @Test + public void testGetServiceIdException() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + try { + jdbcConn.getServiceId(mockConn, 3, "service1"); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetHostId() throws Exception { + + // first time success from mysql, second time failure so + // we can verify we get the value from our cache + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn(9).when(mockResultSet).getInt(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals(9, jdbcConn.getHostId(mockConn, "host1")); + assertEquals(9, jdbcConn.getHostId(mockConn, "host1")); + + jdbcConn.close(); + } + + @Test + public void testGetHostIdException() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + try { + jdbcConn.getHostId(mockConn, "host1"); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetDomainAllFields() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn("my-domain").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_NAME); + Mockito.doReturn(new java.sql.Timestamp(1454358916)).when(mockResultSet).getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED); + Mockito.doReturn(true).when(mockResultSet).getBoolean(ZMSConsts.DB_COLUMN_ENABLED); + Mockito.doReturn(true).when(mockResultSet).getBoolean(ZMSConsts.DB_COLUMN_AUDIT_ENABLED); + Mockito.doReturn("my own domain").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_DESCRIPTION); + Mockito.doReturn("cloud_services").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_ORG); + Mockito.doReturn("e5e97240-e94e-11e4-8163-6d083f3f473f").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_UUID); + Mockito.doReturn("12345").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_ACCOUNT); + Mockito.doReturn(1001).when(mockResultSet).getInt(ZMSConsts.DB_COLUMN_PRODUCT_ID); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Domain domain = jdbcConn.getDomain("my-domain"); + assertNotNull(domain); + assertEquals("my-domain", domain.getName()); + assertTrue(domain.getEnabled()); + assertTrue(domain.getAuditEnabled()); + assertEquals("my own domain", domain.getDescription()); + assertEquals("cloud_services", domain.getOrg()); + assertEquals(UUID.fromString("e5e97240-e94e-11e4-8163-6d083f3f473f"), domain.getId()); + jdbcConn.close(); + } + + @Test + public void testGetDomainException() throws Exception { + + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + try { + jdbcConn.getDomain("my-domain"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Domain domain = new Domain().setName("my-domain") + .setEnabled(true) + .setAuditEnabled(false) + .setDescription("my domain") + .setId(UUID.fromString("e5e97240-e94e-11e4-8163-6d083f3f473f")) + .setOrg("cloud_services"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + boolean requestSuccess = jdbcConn.insertDomain(domain); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "my domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "cloud_services"); + Mockito.verify(mockPrepStmt, times(1)).setString(4, "e5e97240-e94e-11e4-8163-6d083f3f473f"); + Mockito.verify(mockPrepStmt, times(1)).setBoolean(5, true); + Mockito.verify(mockPrepStmt, times(1)).setBoolean(6, false); + Mockito.verify(mockPrepStmt, times(1)).setString(7, ""); + Mockito.verify(mockPrepStmt, times(1)).setInt(8, 0); + jdbcConn.close(); + } + + @Test + public void testInsertDomainWithAccountInfo() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Domain domain = new Domain().setName("my-domain") + .setEnabled(true) + .setAuditEnabled(false) + .setDescription("my domain") + .setId(UUID.fromString("e5e97240-e94e-11e4-8163-6d083f3f473f")) + .setOrg("cloud_services") + .setAccount("123456789") + .setYpmId(Integer.valueOf(1011)); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + boolean requestSuccess = jdbcConn.insertDomain(domain); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "my domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "cloud_services"); + Mockito.verify(mockPrepStmt, times(1)).setString(4, "e5e97240-e94e-11e4-8163-6d083f3f473f"); + Mockito.verify(mockPrepStmt, times(1)).setBoolean(5, true); + Mockito.verify(mockPrepStmt, times(1)).setBoolean(6, false); + Mockito.verify(mockPrepStmt, times(1)).setString(7, "123456789"); + Mockito.verify(mockPrepStmt, times(1)).setInt(8, 1011); + jdbcConn.close(); + } + + @Test + public void testInsertDomainNullFields() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Domain domain = new Domain().setName("my-domain") + .setEnabled(true) + .setAuditEnabled(false); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + boolean requestSuccess = jdbcConn.insertDomain(domain); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, ""); + Mockito.verify(mockPrepStmt, times(1)).setString(3, ""); + Mockito.verify(mockPrepStmt, times(1)).setString(4, ""); + Mockito.verify(mockPrepStmt, times(1)).setBoolean(5, true); + Mockito.verify(mockPrepStmt, times(1)).setBoolean(6, false); + jdbcConn.close(); + } + + @Test + public void testInsertDomainException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Domain domain = new Domain().setName("my-domain") + .setEnabled(true) + .setAuditEnabled(false) + .setDescription("my domain") + .setId(UUID.fromString("e5e97240-e94e-11e4-8163-6d083f3f473f")) + .setOrg("cloud_services"); + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.insertDomain(domain); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdateDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Domain domain = new Domain().setName("my-domain") + .setEnabled(true) + .setAuditEnabled(false) + .setDescription("my domain") + .setId(UUID.fromString("e5e97240-e94e-11e4-8163-6d083f3f473f")) + .setOrg("cloud_services") + .setAccount("123456789") + .setYpmId(Integer.valueOf(1011)); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + boolean requestSuccess = jdbcConn.updateDomain(domain); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "cloud_services"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "e5e97240-e94e-11e4-8163-6d083f3f473f"); + Mockito.verify(mockPrepStmt, times(1)).setBoolean(4, true); + Mockito.verify(mockPrepStmt, times(1)).setBoolean(5, false); + Mockito.verify(mockPrepStmt, times(1)).setString(6, "123456789"); + Mockito.verify(mockPrepStmt, times(1)).setInt(7, 1011); + Mockito.verify(mockPrepStmt, times(1)).setString(8, "my-domain"); + jdbcConn.close(); + } + + @Test + public void testUpdateDomainNullFields() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Domain domain = new Domain().setName("my-domain") + .setEnabled(true) + .setAuditEnabled(false); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + boolean requestSuccess = jdbcConn.updateDomain(domain); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, ""); + Mockito.verify(mockPrepStmt, times(1)).setString(2, ""); + Mockito.verify(mockPrepStmt, times(1)).setString(3, ""); + Mockito.verify(mockPrepStmt, times(1)).setBoolean(4, true); + Mockito.verify(mockPrepStmt, times(1)).setBoolean(5, false); + Mockito.verify(mockPrepStmt, times(1)).setString(6, ""); + Mockito.verify(mockPrepStmt, times(1)).setInt(7, 0); + Mockito.verify(mockPrepStmt, times(1)).setString(8, "my-domain"); + jdbcConn.close(); + } + + @Test + public void testUpdateDomainException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Domain domain = new Domain().setName("my-domain") + .setEnabled(true) + .setAuditEnabled(false) + .setDescription("my domain") + .setId(UUID.fromString("e5e97240-e94e-11e4-8163-6d083f3f473f")) + .setOrg("cloud_services"); + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.updateDomain(domain); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + boolean requestSuccess = jdbcConn.deleteDomain("my-domain"); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + jdbcConn.close(); + } + + @Test + public void testUpdateDomainModTimestampSuccess() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + boolean requestSuccess = jdbcConn.updateDomainModTimestamp("my-domain"); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + jdbcConn.close(); + } + + @Test + public void testUpdateDomainModTimestampFailure() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(0).when(mockPrepStmt).executeUpdate(); + boolean requestSuccess = jdbcConn.updateDomainModTimestamp("my-domain"); + assertFalse(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + jdbcConn.close(); + } + + @Test + public void testUpdateDomainModTimestampException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.updateDomainModTimestamp("my-domain"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteDomainException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.deleteDomain("my-domain"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testListDomains() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("zdomain") + .thenReturn("adomain") + .thenReturn("bdomain"); + + List domains = jdbcConn.listDomains(null, 0); + + // data back is sorted + + assertEquals(3, domains.size()); + assertEquals("adomain", domains.get(0)); + assertEquals("bdomain", domains.get(1)); + assertEquals("zdomain", domains.get(2)); + jdbcConn.close(); + } + + @Test + public void testListDomainsException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.listDomains(null, 0); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetDomainModTimestampSuccess() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(new java.sql.Timestamp(1454358916)).when(mockResultSet).getTimestamp(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + long modTime = jdbcConn.getDomainModTimestamp("my-domain"); + assertEquals(1454358916, modTime); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + jdbcConn.close(); + } + + @Test + public void testGetDomainModTimestampFailure() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + long modTime = jdbcConn.getDomainModTimestamp("my-domain"); + assertEquals(0, modTime); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + jdbcConn.close(); + } + + @Test + public void testGetDomainModTimestampException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + assertEquals(0, jdbcConn.getDomainModTimestamp("my-domain")); + jdbcConn.close(); + } + + @Test + public void testGetRole() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn("role1").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_NAME); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_TRUST); + Mockito.doReturn(new java.sql.Timestamp(1454358916)).when(mockResultSet).getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Role role = jdbcConn.getRole("my-domain", "role1"); + assertNotNull(role); + assertEquals("my-domain:role.role1", role.getName()); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + jdbcConn.close(); + } + + @Test + public void testGetRoleNotFound() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Role role = jdbcConn.getRole("my-domain", "role1"); + assertNull(role); + jdbcConn.close(); + } + + @Test + public void testGetRoleTrust() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn("role1").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_NAME); + Mockito.doReturn("trust.domain").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_TRUST); + Mockito.doReturn(new java.sql.Timestamp(1454358916)).when(mockResultSet).getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Role role = jdbcConn.getRole("my-domain", "role1"); + assertNotNull(role); + assertEquals("my-domain:role.role1", role.getName()); + assertEquals("trust.domain", role.getTrust()); + jdbcConn.close(); + } + + @Test + public void testGetRoleException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.getRole("my-domain", "role1"); + fail(); + } catch (ResourceException ex) { + assertEquals(500, ex.getCode()); + } + jdbcConn.close(); + } + + @Test + public void testInsertRole() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Role role = new Role().setName("my-domain:role.role1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + boolean requestSuccess = jdbcConn.insertRole("my-domain", role); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(1, "role1"); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(3, ""); + jdbcConn.close(); + } + + @Test + public void testInsertRoleInvalidRoleDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Role role = new Role().setName("my-domain2:role.role1"); + + try { + jdbcConn.insertRole("my-domain", role); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertRoleInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Role role = new Role().setName("my-domain:role.role1"); + Mockito.when(mockResultSet.next()).thenReturn(false); // domain id failure + + try { + jdbcConn.insertRole("my-domain", role); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertRoleWithTrust() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Role role = new Role().setName("my-domain:role.role1").setTrust("trust_domain"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5); // return domain + + boolean requestSuccess = jdbcConn.insertRole("my-domain", role); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(1, "role1"); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "trust_domain"); + jdbcConn.close(); + } + + @Test + public void testInsertRoleException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Role role = new Role().setName("my-domain:role.role1"); + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.insertRole("my-domain", role); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdateRole() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Role role = new Role().setName("my-domain:role.role1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5) // return domain id + .thenReturn(4); //role id + + boolean requestSuccess = jdbcConn.updateRole("my-domain", role); + assertTrue(requestSuccess); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + // get role id + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + // update role + Mockito.verify(mockPrepStmt, times(1)).setString(1, ""); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 4); + jdbcConn.close(); + } + + @Test + public void testUpdateRoleWithTrust() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Role role = new Role().setName("my-domain:role.role1").setTrust("trust_domain"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // role id + + boolean requestSuccess = jdbcConn.updateRole("my-domain", role); + assertTrue(requestSuccess); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + // get role id + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + // update role + Mockito.verify(mockPrepStmt, times(1)).setString(1, "trust_domain"); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 7); + jdbcConn.close(); + } + + @Test + public void testUpdateRoleInvalidRoleDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Role role = new Role().setName("my-domain2:role.role1"); + + try { + jdbcConn.updateRole("my-domain", role); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdateRoleInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Role role = new Role().setName("my-domain:role.role1"); + Mockito.when(mockResultSet.next()).thenReturn(false); // domain id failure + + try { + jdbcConn.updateRole("my-domain", role); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdateRoleInvalidRoleId() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5); // return domain id + + Role role = new Role().setName("my-domain:role.role1"); + + try { + jdbcConn.updateRole("my-domain", role); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdateRoleException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Role role = new Role().setName("my-domain:role.role1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.updateRole("my-domain", role); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdateRoleModTimestampSuccess() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // role id + + boolean requestSuccess = jdbcConn.updateRoleModTimestamp("my-domain", "role1"); + assertTrue(requestSuccess); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + // get role id + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + // update role time-stamp + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + jdbcConn.close(); + } + + @Test + public void testUpdateRoleModTimestampFailure() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(0).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // role id + + boolean requestSuccess = jdbcConn.updateRoleModTimestamp("my-domain", "role1"); + assertFalse(requestSuccess); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + // get role id + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + // update role time-stamp + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + jdbcConn.close(); + } + + @Test + public void testUpdateRoleModTimestampException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.updateRoleModTimestamp("my-domain", "role1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteRole() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + boolean requestSuccess = jdbcConn.deleteRole("my-domain", "role1"); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + jdbcConn.close(); + } + + @Test + public void testDeleteRoleinvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(false); + + try { + jdbcConn.deleteRole("my-domain", "role1"); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteRoleException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.deleteRole("my-domain", "role1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testListRoles() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(1)) + .thenReturn("zrole") + .thenReturn("arole") + .thenReturn("brole"); + + List roles = jdbcConn.listRoles("my-domain"); + + // data back is sorted + + assertEquals(3, roles.size()); + assertEquals("arole", roles.get(0)); + assertEquals("brole", roles.get(1)); + assertEquals("zrole", roles.get(2)); + jdbcConn.close(); + } + + @Test + public void testListRolesInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + try { + jdbcConn.listRoles("my-domain"); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testListRolesException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockResultSet.next()) + .thenReturn(true); // this one is for domain id + + Mockito.when(mockPrepStmt.executeQuery()) + .thenReturn(mockResultSet) + .thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.listRoles("my-domain"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testExtractRoleName() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals("role1", jdbcConn.extractRoleName("my-domain1", "my-domain1:role.role1")); + assertEquals("role1.role2", jdbcConn.extractRoleName("my-domain1", "my-domain1:role.role1.role2")); + + // invalid roles names + assertNull(jdbcConn.extractRoleName("my-domain1", "my-domain1:role1")); + assertNull(jdbcConn.extractRoleName("my-domain1", "my-domain2:role.role1")); + assertNull(jdbcConn.extractRoleName("my-domain1", "my-domain11:role.role1")); + assertNull(jdbcConn.extractRoleName("my-domain1", ":role.role1")); + assertNull(jdbcConn.extractRoleName("my-domain1", "role1")); + assertNull(jdbcConn.extractRoleName("my-domain1", "role1.role2")); + jdbcConn.close(); + } + + @Test + public void testListRoleMembers() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5).thenReturn(7); // return domain/trust id + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for role id + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(1)) + .thenReturn("zdomain.user1") + .thenReturn("adomain.storage") + .thenReturn("bdomain.user2"); + + List roles = jdbcConn.listRoleMembers("my-domain", "role1"); + + // data back is sorted + + assertEquals(3, roles.size()); + assertEquals("adomain.storage", roles.get(0)); + assertEquals("bdomain.user2", roles.get(1)); + assertEquals("zdomain.user1", roles.get(2)); + jdbcConn.close(); + } + + @Test + public void testListRoleMembersInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // invalid domain + + try { + jdbcConn.listRoleMembers("my-domain", "role1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testListRoleMembersInvalidRole() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5); // return domain id + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(false); // this one is for role id + + try { + jdbcConn.listRoleMembers("my-domain", "role1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testListRoleMembersException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5).thenReturn(7); // return domain id + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true); // this one is for role id + + Mockito.when(mockPrepStmt.executeQuery()) + .thenReturn(mockResultSet) + .thenReturn(mockResultSet) + .thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.listRoleMembers("my-domain", "role1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testParseRoleMember() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + StringBuilder domain = new StringBuilder(512); + StringBuilder name = new StringBuilder(512); + assertTrue(jdbcConn.parsePrincipal("user.user", domain, name)); + assertEquals("user", domain.toString()); + assertEquals("user", name.toString()); + + domain.setLength(0); + name.setLength(0); + assertTrue(jdbcConn.parsePrincipal("coretech.storage.service", domain, name)); + assertEquals("coretech.storage", domain.toString()); + assertEquals("service", name.toString()); + + assertFalse(jdbcConn.parsePrincipal(".coretech", domain, name)); + assertFalse(jdbcConn.parsePrincipal("coretech.storage.service.", domain, name)); + assertFalse(jdbcConn.parsePrincipal("service", domain, name)); + assertFalse(jdbcConn.parsePrincipal("", domain, name)); + jdbcConn.close(); + } + + @Test + public void testInsertRoleMember() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7) // role id + .thenReturn(9); // principal id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for role id + .thenReturn(true); // principal id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.insertRoleMember("my-domain", "role1", "user.user1", + "user.admin", "audit-ref"); + + // this is combined for all operations above + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "user.user1"); + + // we need additional operation for the audit log + Mockito.verify(mockPrepStmt, times(2)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 9); + + // the rest of the audit log details + + Mockito.verify(mockPrepStmt, times(1)).setString(2, "user.admin"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "user.user1"); + Mockito.verify(mockPrepStmt, times(1)).setString(4, "ADD"); + Mockito.verify(mockPrepStmt, times(1)).setString(5, "audit-ref"); + + assertTrue(requestSuccess); + jdbcConn.close(); + } + + @Test + public void testInsertRoleMemberNewPrincipal() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7) // role id + .thenReturn(8) // principal domain id + .thenReturn(9); // principal id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for role id + .thenReturn(true) // this one is for valid principal domain + .thenReturn(false) // principal does not exist + .thenReturn(true); // get last id (for new principal) + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.insertRoleMember("my-domain", "role1", "user.user1", + "user.admin", "audit-ref"); + + // this is combined for all operations above + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "user"); + + // we're going to have 2 sets of operations for principal name + + Mockito.verify(mockPrepStmt, times(2)).setString(1, "user.user1"); + + // we need additional operation for the audit log + Mockito.verify(mockPrepStmt, times(2)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 9); + + // the rest of the audit log details + + Mockito.verify(mockPrepStmt, times(1)).setString(2, "user.admin"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "user.user1"); + Mockito.verify(mockPrepStmt, times(1)).setString(4, "ADD"); + Mockito.verify(mockPrepStmt, times(1)).setString(5, "audit-ref"); + + assertTrue(requestSuccess); + jdbcConn.close(); + } + + @Test + public void testInsertRoleMemberException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7) // role id + .thenReturn(9) // member domain id + .thenReturn(11); // principal id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for role id + .thenReturn(true) // member domain id + .thenReturn(true); // principal id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.insertRoleMember("my-domain", "role1", "user.user1", + "user.admin", "audit-ref"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertRoleMemberNewPrincipalFailure() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7) // role id + .thenReturn(8) // principal domain id + .thenReturn(9); // principal id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for role id + .thenReturn(true) // this one is for valid principal domain + .thenReturn(false); // principal does not exist + + // principal add returns 0 + + Mockito.doReturn(0).when(mockPrepStmt).executeUpdate(); + + try { + jdbcConn.insertRoleMember("my-domain", "role1", "user.user1", + "user.admin", "audit-ref"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 500); + } + + jdbcConn.close(); + } + + @Test + public void testGetRoleMemberYes() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); // yes a member + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // role id + + Membership membership = jdbcConn.getRoleMember("my-domain", "role1", "user.user1"); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "user.user1"); + + assertEquals(membership.getMemberName(), "user.user1"); + assertEquals(membership.getRoleName(), "my-domain:role.role1"); + assertTrue(membership.getIsMember()); + jdbcConn.close(); + } + + @Test + public void testGetRoleMemberNo() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()) + .thenReturn(true) // domain id + .thenReturn(true) // rold id + .thenReturn(false); // not a member + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // role id + + Membership membership = jdbcConn.getRoleMember("my-domain", "role1", "user.user1"); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "user.user1"); + + assertEquals(membership.getMemberName(), "user.user1"); + assertEquals(membership.getRoleName(), "my-domain:role.role1"); + assertFalse(membership.getIsMember()); + jdbcConn.close(); + } + + @Test + public void testGetRoleMemberInvalidPrincipal() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); // yes a member + + try { + jdbcConn.getRoleMember("my-domain", "role1", "user1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetRoleMemberException() throws Exception { + + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + try { + jdbcConn.getRoleMember("my-domain", "role1", "user.user1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteRoleMember() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7) // role id + .thenReturn(9) // member domain id + .thenReturn(11); // principal id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for role id + .thenReturn(true) // member domain id + .thenReturn(true); // principal id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.deleteRoleMember("my-domain", "role1", "user.user1", + "user.admin", "audit-ref"); + assertTrue(requestSuccess); + + // this is combined for all operations above + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "user.user1"); + + // we need additional operation for the audit log + Mockito.verify(mockPrepStmt, times(2)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 9); + + // the rest of the audit log details + + Mockito.verify(mockPrepStmt, times(1)).setString(2, "user.admin"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "user.user1"); + Mockito.verify(mockPrepStmt, times(1)).setString(4, "DELETE"); + Mockito.verify(mockPrepStmt, times(1)).setString(5, "audit-ref"); + + jdbcConn.close(); + } + + @Test + public void testDeleteRoleMemberInvalidDomain() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + try { + jdbcConn.deleteRoleMember("my-domain", "role1", "user.user1", + "user.admin", "audit-ref"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteRoleMemberInvalidRole() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(false); // this one is for role id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + try { + jdbcConn.deleteRoleMember("my-domain", "role1", "user.user1", + "user.admin", "audit-ref"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteRoleMemberInvalidPrincipalId() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // role id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for role id + .thenReturn(false); // principal id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + try { + jdbcConn.deleteRoleMember("my-domain", "role1", "user.user1", + "user.admin", "audit-ref"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetPolicy() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn("policy1").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_NAME); + Mockito.doReturn(new java.sql.Timestamp(1454358916)).when(mockResultSet).getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Policy policy = jdbcConn.getPolicy("my-domain", "policy1"); + assertNotNull(policy); + assertEquals("my-domain:policy.policy1", policy.getName()); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "policy1"); + jdbcConn.close(); + } + + @Test + public void testGetPolicyException() throws Exception { + + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + try { + jdbcConn.getPolicy("my-domain", "policy1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertPolicy() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Policy policy = new Policy().setName("my-domain:policy.policy1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + boolean requestSuccess = jdbcConn.insertPolicy("my-domain", policy); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(1, "policy1"); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 5); + jdbcConn.close(); + } + + @Test + public void testInsertPolicyInvalidName() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Policy policy = new Policy().setName("policy1"); + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + try { + jdbcConn.insertPolicy("my-domain", policy); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + jdbcConn.close(); + } + + @Test + public void testInsertPolicyException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Policy policy = new Policy().setName("my-domain:policy.policy1"); + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.insertPolicy("my-domain", policy); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdatePolicy() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Policy policy = new Policy().setName("my-domain:policy.policy1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5) // return domain id + .thenReturn(4); //policy id + + boolean requestSuccess = jdbcConn.updatePolicy("my-domain", policy); + assertTrue(requestSuccess); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + // get policy id + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "policy1"); + // update policy + Mockito.verify(mockPrepStmt, times(1)).setString(1, "policy1"); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 4); + jdbcConn.close(); + } + + @Test + public void testUpdatePolicyInvalidName() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Policy policy = new Policy().setName("policy1"); + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + try { + jdbcConn.updatePolicy("my-domain", policy); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + jdbcConn.close(); + } + + @Test + public void testUpdatePolicyException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Policy policy = new Policy().setName("my-domain:policy.policy1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.updatePolicy("my-domain", policy); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeletePolicy() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + boolean requestSuccess = jdbcConn.deletePolicy("my-domain", "policy1"); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "policy1"); + jdbcConn.close(); + } + + @Test + public void testDeletePolicyException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.deletePolicy("my-domain", "policy1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testListPolicies() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(1)) + .thenReturn("zpolicy") + .thenReturn("apolicy") + .thenReturn("bpolicy"); + + List policies = jdbcConn.listPolicies("my-domain", null); + + // data back is sorted + + assertEquals(3, policies.size()); + assertEquals("apolicy", policies.get(0)); + assertEquals("bpolicy", policies.get(1)); + assertEquals("zpolicy", policies.get(2)); + jdbcConn.close(); + } + + @Test + public void testExtractPolicyName() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals("policy1", jdbcConn.extractPolicyName("my-domain1", "my-domain1:policy.policy1")); + assertEquals("policy1.policy2", jdbcConn.extractPolicyName("my-domain1", "my-domain1:policy.policy1.policy2")); + + // invalid policies names + assertNull(jdbcConn.extractPolicyName("my-domain1", "my-domain1:policy1")); + assertNull(jdbcConn.extractPolicyName("my-domain1", "my-domain2:policy.policy1")); + assertNull(jdbcConn.extractPolicyName("my-domain1", "my-domain11:policy.policy1")); + assertNull(jdbcConn.extractPolicyName("my-domain1", ":policy.policy1")); + assertNull(jdbcConn.extractPolicyName("my-domain1", "policy1")); + assertNull(jdbcConn.extractPolicyName("my-domain1", "policy1.policy2")); + jdbcConn.close(); + } + + @Test + public void testSkipAwsUserQuery() throws Exception { + + try (JDBCConnection jdbcConn = new JDBCConnection(mockConn, true)) { + Map map = new HashMap() { + private static final long serialVersionUID = -8689695626417810614L; + { + put("zms", "domain1"); + } + { + put("zms", "domain2"); + } + }; + assertFalse(jdbcConn.skipAwsUserQuery(map, "queryP1", "zms", "zms")); + jdbcConn.skipAwsUserQuery(map, null, "zms", "user"); + } + } + + @Test + public void testInsertAssertion() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Assertion assertion = new Assertion() + .setAction("read") + .setEffect(AssertionEffect.ALLOW) + .setResource("my-domain:*") + .setRole("my-domain:role.role1"); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // policy id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for policy id + .thenReturn(false); // insertion is not found + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.insertAssertion("my-domain", "policy1", assertion); + assertTrue(requestSuccess); + + // getting domain and policy ids + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "policy1"); + + // assertion statement - twice once for checking if it exists + // and second time for inserting + + Mockito.verify(mockPrepStmt, times(2)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(2)).setString(2, "role1"); + Mockito.verify(mockPrepStmt, times(2)).setString(3, "my-domain:*"); + Mockito.verify(mockPrepStmt, times(2)).setString(4, "read"); + Mockito.verify(mockPrepStmt, times(2)).setString(5, "ALLOW"); + jdbcConn.close(); + } + + @Test + public void testInsertAssertionDuplicate() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Assertion assertion = new Assertion() + .setAction("read") + .setEffect(AssertionEffect.ALLOW) + .setResource("my-domain:*") + .setRole("my-domain:role.role1"); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // policy id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for policy id + .thenReturn(true); // insertion is found + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.insertAssertion("my-domain", "policy1", assertion); + assertTrue(requestSuccess); + + // getting domain and policy ids + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "policy1"); + + // assertion statement + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "role1"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "my-domain:*"); + Mockito.verify(mockPrepStmt, times(1)).setString(4, "read"); + Mockito.verify(mockPrepStmt, times(1)).setString(5, "ALLOW"); + jdbcConn.close(); + } + @Test + public void testInsertAssertionInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Assertion assertion = new Assertion() + .setAction("read") + .setEffect(AssertionEffect.ALLOW) + .setResource("my-domain:*") + .setRole("my-domain:role.role1"); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + try { + jdbcConn.insertAssertion("my-domain", "policy1", assertion); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertAssertionInvalidRoleName() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Assertion assertion = new Assertion() + .setAction("read") + .setEffect(AssertionEffect.ALLOW) + .setResource("my-domain:*") + .setRole("invalid_role"); + + try { + jdbcConn.insertAssertion("my-domain", "policy1", assertion); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertAssertionInvalidPolicy() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Assertion assertion = new Assertion() + .setAction("read") + .setEffect(AssertionEffect.ALLOW) + .setResource("my-domain:*") + .setRole("my-domain:role.role1"); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(false); // this one is for policy id + + try { + jdbcConn.insertAssertion("my-domain", "policy1", assertion); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertAssertionException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Assertion assertion = new Assertion() + .setAction("read") + .setEffect(AssertionEffect.ALLOW) + .setResource("my-domain:*") + .setRole("my-domain:role.role1"); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // policy id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for policy id + .thenReturn(false); // assume insertion is not found + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.insertAssertion("my-domain", "policy1", assertion); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteAssertion() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // policy id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true); // this one is for policy id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.deleteAssertion("my-domain", "policy1", (long) 101); + assertTrue(requestSuccess); + + // getting domain and policy ids + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "policy1"); + + // assertion statement + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 101); + jdbcConn.close(); + } + + @Test + public void testDeleteAssertionInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + try { + jdbcConn.deleteAssertion("my-domain", "policy1", (long) 101); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteAssertionInvalidPolicy() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(false); // this one is for policy id + + try { + jdbcConn.deleteAssertion("my-domain", "policy1", (long) 101); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteAssertionException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // policy id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true); // this one is for policy id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.deleteAssertion("my-domain", "policy1", (long) 101); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetServiceIdentity() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn("policy1").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_NAME); + Mockito.doReturn(new java.sql.Timestamp(1454358916)).when(mockResultSet).getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_EXECTUABLE); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_SVC_GROUP); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_SVC_USER); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_PROVIDER_ENDPOINT); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + ServiceIdentity service = jdbcConn.getServiceIdentity("my-domain", "service1"); + assertNotNull(service); + assertEquals("my-domain.service1", service.getName()); + assertNull(service.getExecutable()); + assertNull(service.getGroup()); + assertNull(service.getUser()); + assertNull(service.getProviderEndpoint()); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + jdbcConn.close(); + } + + @Test + public void testGetServiceIdentityNoMatch() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + ServiceIdentity service = jdbcConn.getServiceIdentity("my-domain", "service1"); + assertNull(service); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + jdbcConn.close(); + } + + @Test + public void testGetServiceIdentityAllFields() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn("policy1").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_NAME); + Mockito.doReturn(new java.sql.Timestamp(1454358916)).when(mockResultSet).getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED); + Mockito.doReturn("/usr/bin64/athenz").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_EXECTUABLE); + Mockito.doReturn("users").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_SVC_GROUP); + Mockito.doReturn("root").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_SVC_USER); + Mockito.doReturn("http://server.athenzcompany.com").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_PROVIDER_ENDPOINT); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + ServiceIdentity service = jdbcConn.getServiceIdentity("my-domain", "service1"); + assertNotNull(service); + assertEquals("my-domain.service1", service.getName()); + assertEquals("/usr/bin64/athenz", service.getExecutable()); + assertEquals("users", service.getGroup()); + assertEquals("root", service.getUser()); + assertEquals("http://server.athenzcompany.com", service.getProviderEndpoint().toString()); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + jdbcConn.close(); + } + + @Test + public void testGetServiceException() throws Exception { + + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + try { + jdbcConn.getServiceIdentity("my-domain", "service1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertServiceIdentity() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + ServiceIdentity service = new ServiceIdentity().setName("my-domain.service1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5); // return domain id + + boolean requestSuccess = jdbcConn.insertServiceIdentity("my-domain", service); + assertTrue(requestSuccess); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + // update service + Mockito.verify(mockPrepStmt, times(1)).setString(1, "service1"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, ""); + Mockito.verify(mockPrepStmt, times(1)).setString(3, ""); + Mockito.verify(mockPrepStmt, times(1)).setString(4, ""); + Mockito.verify(mockPrepStmt, times(1)).setString(5, ""); + jdbcConn.close(); + } + + @Test + public void testInsertServiceIdentityInvalidName() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + ServiceIdentity service = new ServiceIdentity().setName("service1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5) // return domain id + .thenReturn(4); //service id + + try { + jdbcConn.insertServiceIdentity("my-domain", service); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + jdbcConn.close(); + } + + @Test + public void testInsertServiceIdentityAllFields() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + ServiceIdentity service = new ServiceIdentity() + .setName("my-domain.service1") + .setExecutable("/usr/bin64/test.sh") + .setGroup("users") + .setUser("root") + .setProviderEndpoint("http://server.athenzcompany.com"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5); // return domain id + + boolean requestSuccess = jdbcConn.insertServiceIdentity("my-domain", service); + assertTrue(requestSuccess); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + // update service + Mockito.verify(mockPrepStmt, times(1)).setString(1, "service1"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "http://server.athenzcompany.com"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "/usr/bin64/test.sh"); + Mockito.verify(mockPrepStmt, times(1)).setString(4, "root"); + Mockito.verify(mockPrepStmt, times(1)).setString(5, "users"); + Mockito.verify(mockPrepStmt, times(1)).setInt(6, 5); + jdbcConn.close(); + } + + @Test + public void testInsertServiceException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + ServiceIdentity service = new ServiceIdentity().setName("my-domain.service1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5); // return domain id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.insertServiceIdentity("my-domain", service); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdateServiceIdentity() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + ServiceIdentity service = new ServiceIdentity().setName("my-domain.service1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5) // return domain id + .thenReturn(4); //service id + + boolean requestSuccess = jdbcConn.updateServiceIdentity("my-domain", service); + assertTrue(requestSuccess); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + // get service id + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + // update service + Mockito.verify(mockPrepStmt, times(1)).setString(1, ""); + Mockito.verify(mockPrepStmt, times(1)).setString(2, ""); + Mockito.verify(mockPrepStmt, times(1)).setString(3, ""); + Mockito.verify(mockPrepStmt, times(1)).setString(4, ""); + Mockito.verify(mockPrepStmt, times(1)).setInt(5, 4); + jdbcConn.close(); + } + + @Test + public void testUpdateServiceIdentityInvalidName() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + ServiceIdentity service = new ServiceIdentity().setName("service1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5) // return domain id + .thenReturn(4); //service id + + try { + jdbcConn.updateServiceIdentity("my-domain", service); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + jdbcConn.close(); + } + + @Test + public void testUpdateServiceIdentityAllFields() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + ServiceIdentity service = new ServiceIdentity() + .setName("my-domain.service1") + .setExecutable("/usr/bin64/test.sh") + .setGroup("users") + .setUser("root") + .setProviderEndpoint("http://server.athenzcompany.com"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5) // return domain id + .thenReturn(4); //service id + + boolean requestSuccess = jdbcConn.updateServiceIdentity("my-domain", service); + assertTrue(requestSuccess); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + // get service id + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + // update service + Mockito.verify(mockPrepStmt, times(1)).setString(1, "http://server.athenzcompany.com"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "/usr/bin64/test.sh"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "root"); + Mockito.verify(mockPrepStmt, times(1)).setString(4, "users"); + Mockito.verify(mockPrepStmt, times(1)).setInt(5, 4); + jdbcConn.close(); + } + + @Test + public void testUpdateServiceIdentityException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + ServiceIdentity service = new ServiceIdentity().setName("my-domain.service1"); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5) // return domain id + .thenReturn(4); //service id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.updateServiceIdentity("my-domain", service); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteServiceIdentity() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + boolean requestSuccess = jdbcConn.deleteServiceIdentity("my-domain", "service1"); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + jdbcConn.close(); + } + + @Test + public void testDeleteServiceIdentityException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.deleteServiceIdentity("my-domain", "service1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testListServiceIdentities() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(1)) + .thenReturn("zservice") + .thenReturn("aservice") + .thenReturn("bservice"); + + List services = jdbcConn.listServiceIdentities("my-domain"); + + // data back is sorted + + assertEquals(3, services.size()); + assertEquals("aservice", services.get(0)); + assertEquals("bservice", services.get(1)); + assertEquals("zservice", services.get(2)); + jdbcConn.close(); + } + + @Test + public void testExtractServiceName() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals("service1", jdbcConn.extractServiceName("my-domain1", "my-domain1.service1")); + assertEquals("service1", jdbcConn.extractServiceName("my-domain1.domain2", "my-domain1.domain2.service1")); + + // invalid service names + assertNull(jdbcConn.extractServiceName("my-domain1", "my-domain1:service1")); + assertNull(jdbcConn.extractServiceName("my-domain1", "my-domain2.service1")); + assertNull(jdbcConn.extractServiceName("my-domain1", "my-domain11:service.service1")); + assertNull(jdbcConn.extractServiceName("my-domain1", ".service1")); + assertNull(jdbcConn.extractServiceName("my-domain1", "service1")); + assertNull(jdbcConn.extractServiceName("my-domain1", "service1.service2")); + jdbcConn.close(); + } + + @Test + public void testSaveValue() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals("test1", jdbcConn.saveValue("test1")); + assertNull(jdbcConn.saveValue("")); + jdbcConn.close(); + } + + @Test + public void testSaveUriValue() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals("http://server.athenzcompany.com", jdbcConn.saveValue("http://server.athenzcompany.com")); + assertNull(jdbcConn.saveValue("")); + jdbcConn.close(); + } + + @Test + public void testProcessInsertValue() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals("test1", jdbcConn.processInsertValue("test1")); + assertEquals("", jdbcConn.processInsertValue((String) null)); + jdbcConn.close(); + } + + @Test + public void testProcessInsertIntValue() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals(1001, jdbcConn.processInsertValue(Integer.valueOf(1001))); + assertEquals(0, jdbcConn.processInsertValue((Integer) null)); + jdbcConn.close(); + } + + @Test + public void testProcessInsertBooleanValue() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals(true, jdbcConn.processInsertValue(Boolean.valueOf(true), false)); + assertEquals(false, jdbcConn.processInsertValue((Boolean) null, false)); + jdbcConn.close(); + } + + @Test + public void testProcessInsertAssertionAffect() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals("ALLOW", jdbcConn.processInsertValue(AssertionEffect.ALLOW)); + assertEquals("DENY", jdbcConn.processInsertValue(AssertionEffect.DENY)); + assertEquals("ALLOW", jdbcConn.processInsertValue((AssertionEffect) null)); + jdbcConn.close(); + } + + @Test + public void testProcessInsertUriValue() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals("http://server.athenzcompany.com", jdbcConn.processInsertValue("http://server.athenzcompany.com")); + assertEquals("", jdbcConn.processInsertValue((String) null)); + jdbcConn.close(); + } + + @Test + public void testProcessInsertUuidValue() throws Exception { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals("e5e97240-e94e-11e4-8163-6d083f3f473f", jdbcConn.processInsertUuidValue(UUID.fromString("e5e97240-e94e-11e4-8163-6d083f3f473f"))); + assertEquals("", jdbcConn.processInsertUuidValue(null)); + jdbcConn.close(); + } + + @Test + public void testListPublicKeys() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5).thenReturn(7); // return domain/service id + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for service id + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_KEY_ID)) + .thenReturn("zms1.zone1") + .thenReturn("zms2.zone1") + .thenReturn("zms3.zone1"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_KEY_VALUE)) + .thenReturn("Value1") + .thenReturn("Value2") + .thenReturn("Value3"); + + List publicKeys = jdbcConn.listPublicKeys("my-domain", "service1"); + + // data back is sorted + + assertEquals(3, publicKeys.size()); + assertEquals("zms1.zone1", publicKeys.get(0).getId()); + assertEquals("Value1", publicKeys.get(0).getKey()); + assertEquals("zms2.zone1", publicKeys.get(1).getId()); + assertEquals("Value2", publicKeys.get(1).getKey()); + assertEquals("zms3.zone1", publicKeys.get(2).getId()); + assertEquals("Value3", publicKeys.get(2).getKey()); + jdbcConn.close(); + } + + @Test + public void testListAssertions() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5).thenReturn(7); // return domain/policy id + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for policy id + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ROLE)) + .thenReturn("role1") + .thenReturn("role2"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_RESOURCE)) + .thenReturn("my-domain:*") + .thenReturn("my-domain:service.*"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ACTION)) + .thenReturn("*") + .thenReturn("read"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_EFFECT)) + .thenReturn("ALLOW") + .thenReturn("DENY"); + + List assertions = jdbcConn.listAssertions("my-domain", "policy1"); + + assertEquals(2, assertions.size()); + assertEquals("my-domain:role.role1", assertions.get(0).getRole()); + assertEquals("my-domain:*", assertions.get(0).getResource()); + assertEquals("*", assertions.get(0).getAction()); + assertEquals("ALLOW", assertions.get(0).getEffect().toString()); + + assertEquals("my-domain:role.role2", assertions.get(1).getRole()); + assertEquals("my-domain:service.*", assertions.get(1).getResource()); + assertEquals("read", assertions.get(1).getAction()); + assertEquals("DENY", assertions.get(1).getEffect().toString()); + jdbcConn.close(); + } + + @Test + public void testListAssertionsInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + try { + jdbcConn.listAssertions("my-domain", "policy1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetPublicKeyEntry() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // service id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for service id + .thenReturn(true); // for key + + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_KEY_VALUE)) + .thenReturn("Value1"); + + PublicKeyEntry publicKey = jdbcConn.getPublicKeyEntry("my-domain", "service1", "zone1"); + assertNotNull(publicKey); + assertEquals("Value1", publicKey.getKey()); + assertEquals("zone1", publicKey.getId()); + jdbcConn.close(); + } + + @Test + public void testGetPublicKeyEntryInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + try { + jdbcConn.getPublicKeyEntry("my-domain", "service1", "zone1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetPublicKeyEntryInvalidServiceId() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(false); // this one is for service id + + try { + jdbcConn.getPublicKeyEntry("my-domain", "service1", "zone1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetPublicKeyEntryInvalidKeyId() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // service id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for service id + .thenReturn(false); // for key + + PublicKeyEntry publicKey = jdbcConn.getPublicKeyEntry("my-domain", "service1", "zone1"); + assertNull(publicKey); + jdbcConn.close(); + } + + @Test + public void testGetPublicKeyEntryException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // service id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for service id + .thenReturn(false); // for key + + Mockito.when(mockPrepStmt.executeQuery()) + .thenReturn(mockResultSet) + .thenReturn(mockResultSet) + .thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.getPublicKeyEntry("my-domain", "service1", "zone1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertPublicKeyEntry() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + PublicKeyEntry publicKey = new PublicKeyEntry().setId("zms1").setKey("Value1"); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // service id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true); // this one is for service id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.insertPublicKeyEntry("my-domain", "service1", publicKey); + assertTrue(requestSuccess); + + // getting domain and service ids + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + + // public key entry statement + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "zms1"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "Value1"); + jdbcConn.close(); + } + + @Test + public void testInsertPublicKeyEntryInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + PublicKeyEntry publicKey = new PublicKeyEntry().setId("zms1").setKey("Value1"); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + try { + jdbcConn.insertPublicKeyEntry("my-domain", "service1", publicKey); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertPublicKeyEntryInvalidService() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + PublicKeyEntry publicKey = new PublicKeyEntry().setId("zms1").setKey("Value1"); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(false); // this one is for service id + + try { + jdbcConn.insertPublicKeyEntry("my-domain", "service1", publicKey); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertPublicKeyEntryException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + PublicKeyEntry publicKey = new PublicKeyEntry().setId("zms1").setKey("Value1"); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // service id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true); // this one is for service id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.insertPublicKeyEntry("my-domain", "service1", publicKey); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdatePublicKeyEntry() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + PublicKeyEntry publicKey = new PublicKeyEntry().setId("zms1").setKey("Value1"); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // service id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true); // this one is for service id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.updatePublicKeyEntry("my-domain", "service1", publicKey); + assertTrue(requestSuccess); + + // getting domain and service ids + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + + // public key entry statement + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "Value1"); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 7); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "zms1"); + jdbcConn.close(); + } + + @Test + public void testUpdatePublicKeyEntryInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + PublicKeyEntry publicKey = new PublicKeyEntry().setId("zms1").setKey("Value1"); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + try { + jdbcConn.updatePublicKeyEntry("my-domain", "service1", publicKey); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdatePublicKeyEntryInvalidService() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + PublicKeyEntry publicKey = new PublicKeyEntry().setId("zms1").setKey("Value1"); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(false); // this one is for service id + + try { + jdbcConn.updatePublicKeyEntry("my-domain", "service1", publicKey); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdatePublicKeyEntryException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + PublicKeyEntry publicKey = new PublicKeyEntry().setId("zms1").setKey("Value1"); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // service id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true); // this one is for service id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.updatePublicKeyEntry("my-domain", "service1", publicKey); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeletePublicKeyEntry() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // service id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true); // this one is for service id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.deletePublicKeyEntry("my-domain", "service1", "zms1"); + assertTrue(requestSuccess); + + // getting domain and service ids + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + + // public key entry statement + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "zms1"); + jdbcConn.close(); + } + + @Test + public void testDeletePublicKeyEntryInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + try { + jdbcConn.deletePublicKeyEntry("my-domain", "service1", "zms1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeletePublicKeyEntryInvalidService() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(false); // this one is for service id + + try { + jdbcConn.deletePublicKeyEntry("my-domain", "service1", "zms1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeletePublicKeyEntryException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // service id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true); // this one is for service id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.deletePublicKeyEntry("my-domain", "service1", "zms1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertServiceHost() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7) // service id + .thenReturn(9); // host id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for service id + .thenReturn(true); // this on is for host id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.insertServiceHost("my-domain", "service1", "host1"); + + // this is combined for all operations above + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "host1"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 9); + + assertTrue(requestSuccess); + jdbcConn.close(); + } + + @Test + public void testInsertServiceHostNewHost() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7) // service id + .thenReturn(9); // host id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for service id + .thenReturn(false) // this on is for host does not exist + .thenReturn(true); // insert last id (for new host) + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.insertServiceHost("my-domain", "service1", "host1"); + + // this is combined for all operations above + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + + // 2 times - one for lookup, second time for adding + + Mockito.verify(mockPrepStmt, times(2)).setString(1, "host1"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 9); + + assertTrue(requestSuccess); + jdbcConn.close(); + } + + @Test + public void testInsertServiceHostInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + try { + jdbcConn.insertServiceHost("my-domain", "service1", "host1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertServiceHostInvalidService() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(false); // this one is for service id + + try { + jdbcConn.insertServiceHost("my-domain", "service1", "host1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertServiceHostException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7) // service id + .thenReturn(9); // host id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for service id + .thenReturn(true); // this on is for host id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.insertServiceHost("my-domain", "service1", "host1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteServiceHost() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7) // service id + .thenReturn(9); // host id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for service id + .thenReturn(true); // this on is for host id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.deleteServiceHost("my-domain", "service1", "host1"); + + // this is combined for all operations above + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "service1"); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "host1"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 9); + + assertTrue(requestSuccess); + jdbcConn.close(); + } + + @Test + public void testDeleteServiceHostInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + try { + jdbcConn.deleteServiceHost("my-domain", "service1", "host1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteServiceHostInvalidService() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(false); // this one is for service id + + try { + jdbcConn.deleteServiceHost("my-domain", "service1", "host1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteServiceHostInvalidHost() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // service ie + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for service id + .thenReturn(false); // this one is for host id + + try { + jdbcConn.deleteServiceHost("my-domain", "service1", "host1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteServiceHostException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7) // service id + .thenReturn(9); // host id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for service id + .thenReturn(true); // this on is for host id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.deleteServiceHost("my-domain", "service1", "host1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testListServiceHosts() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // service id + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) // this one is for service id + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("host1") + .thenReturn("host3") + .thenReturn("host2"); + + List serviceHosts = jdbcConn.listServiceHosts("my-domain", "service1"); + + assertEquals(3, serviceHosts.size()); + assertEquals("host1", serviceHosts.get(0)); + assertEquals("host3", serviceHosts.get(1)); + assertEquals("host2", serviceHosts.get(2)); + jdbcConn.close(); + } + + @Test + public void testInsertDomainTemplate() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true); // this one is for domain id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.insertDomainTemplate("my-domain", "platforms", null); + + // this is combined for all operations above + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "platforms"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "platforms"); + + assertTrue(requestSuccess); + jdbcConn.close(); + } + + @Test + public void testInsertDomainTemplateInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + try { + jdbcConn.insertDomainTemplate("my-domain", "platforms", null); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertDomainTemplateNewTemplate() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true); // this one is for domain id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.insertDomainTemplate("my-domain", "platforms", null); + + // this is combined for all operations above + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "platforms"); + + assertTrue(requestSuccess); + jdbcConn.close(); + } + + @Test + public void testInsertDomainTemplateException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // template id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true); // this one is for template id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.insertDomainTemplate("my-domain", "platforms", null); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteDomainTemplate() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + Mockito.when(mockResultSet.next()) + .thenReturn(true); // this one is for domain id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + boolean requestSuccess = jdbcConn.deleteDomainTemplate("my-domain", "platforms", null); + + // this is combined for all operations above + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "platforms"); + + assertTrue(requestSuccess); + jdbcConn.close(); + } + + @Test + public void testDeleteDomainTemplateInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // this one is for domain id + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + + try { + jdbcConn.deleteDomainTemplate("my-domain", "platforms", null); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteDomainTemplateException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // template id + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true); // this one is for template id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.deleteDomainTemplate("my-domain", "platforms", null); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testListDomainTemplates() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(1)) + .thenReturn("vipng") + .thenReturn("platforms") + .thenReturn("user_understanding"); + + List templates = jdbcConn.listDomainTemplates("my-domain"); + + // data back is sorted + + assertEquals(3, templates.size()); + assertEquals("platforms", templates.get(0)); + assertEquals("user_understanding", templates.get(1)); + assertEquals("vipng", templates.get(2)); + jdbcConn.close(); + } + + @Test + public void testListDomainTemplatesException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.listDomainTemplates("my-domain"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testPreparseScanStatementPrefixNullModifiedZero() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + jdbcConn.prepareScanStatement(mockConn, null, 0); + Mockito.verify(mockPrepStmt, times(0)).setString(Mockito.anyInt(), Mockito.isA(String.class)); + jdbcConn.close(); + } + + @Test + public void testPreparseScanStatementPrefixModifiedZero() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + jdbcConn.prepareScanStatement(mockConn, "prefix", 0); + Mockito.verify(mockPrepStmt, times(1)).setString(1, "prefix"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "prefiy"); + jdbcConn.close(); + } + + @Test + public void testPreparseScanStatementPrefixModified() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + jdbcConn.prepareScanStatement(mockConn, "prefix", 100); + Mockito.verify(mockPrepStmt, times(1)).setString(1, "prefix"); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "prefiy"); + Mockito.verify(mockPrepStmt, times(1)).setTimestamp(Matchers.eq(3), Matchers.eq(new java.sql.Timestamp(100)), Matchers.isA(Calendar.class)); + jdbcConn.close(); + } + + @Test + public void testPreparseScanStatementPrefixEmptyModifiedTime() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + jdbcConn.prepareScanStatement(mockConn, "", 100); + Mockito.verify(mockPrepStmt, times(1)).setTimestamp(Matchers.eq(1), Matchers.eq(new java.sql.Timestamp(100)), Matchers.isA(Calendar.class)); + jdbcConn.close(); + } + + @Test + public void testPreparseScanStatementOnlyModifiedTime() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + jdbcConn.prepareScanStatement(mockConn, null, 100); + Mockito.verify(mockPrepStmt, times(1)).setTimestamp(Matchers.eq(1), Matchers.eq(new java.sql.Timestamp(100)), Matchers.isA(Calendar.class)); + jdbcConn.close(); + } + + @Test + public void testPreparseScanByRoleStatement() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.prepareScanByRoleStatement(mockConn, "user.member", "name"); + Mockito.verify(mockPrepStmt, times(1)).setString(Matchers.eq(1), Matchers.eq("user.member")); + Mockito.verify(mockPrepStmt, times(1)).setString(Matchers.eq(2), Matchers.eq("name")); + jdbcConn.close(); + } + + @Test + public void testPreparseScanByRoleStatementOnlyRoleNameNull() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.prepareScanByRoleStatement(mockConn, null, "name"); + Mockito.verify(mockPrepStmt, times(1)).setString(Matchers.eq(1), Matchers.eq("name")); + Mockito.verify(mockPrepStmt, times(0)).setString(Matchers.eq(2), Mockito.isA(String.class)); + jdbcConn.close(); + } + + @Test + public void testPreparseScanByRoleStatementOnlyRoleNameEmpty() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.prepareScanByRoleStatement(mockConn, "", "name"); + Mockito.verify(mockPrepStmt, times(1)).setString(Matchers.eq(1), Matchers.eq("name")); + Mockito.verify(mockPrepStmt, times(0)).setString(Matchers.eq(2), Mockito.isA(String.class)); + jdbcConn.close(); + } + + @Test + public void testPreparseScanByRoleStatementOnlyRoleMemberNull() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.prepareScanByRoleStatement(mockConn, "user.member", null); + Mockito.verify(mockPrepStmt, times(1)).setString(Matchers.eq(1), Matchers.eq("user.member")); + Mockito.verify(mockPrepStmt, times(0)).setString(Matchers.eq(2), Mockito.isA(String.class)); + jdbcConn.close(); + } + + @Test + public void testPreparseScanByRoleStatementOnlyRoleMemberEmpty() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.prepareScanByRoleStatement(mockConn, "user.member", ""); + Mockito.verify(mockPrepStmt, times(1)).setString(Matchers.eq(1), Matchers.eq("user.member")); + Mockito.verify(mockPrepStmt, times(0)).setString(Matchers.eq(2), Mockito.isA(String.class)); + jdbcConn.close(); + } + + @Test + public void testPreparseScanByRoleStatementEmptyRoleMember() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + jdbcConn.prepareScanByRoleStatement(mockConn, null, null); + Mockito.verify(mockPrepStmt, times(0)).setString(Mockito.anyInt(), Mockito.isA(String.class)); + + jdbcConn.prepareScanByRoleStatement(mockConn, null, ""); + Mockito.verify(mockPrepStmt, times(0)).setString(Mockito.anyInt(), Mockito.isA(String.class)); + + jdbcConn.prepareScanByRoleStatement(mockConn, "", null); + Mockito.verify(mockPrepStmt, times(0)).setString(Mockito.anyInt(), Mockito.isA(String.class)); + + jdbcConn.prepareScanByRoleStatement(mockConn, "", ""); + Mockito.verify(mockPrepStmt, times(0)).setString(Mockito.anyInt(), Mockito.isA(String.class)); + + jdbcConn.close(); + } + + @Test + public void testListEntities() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // this one is for domain id + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(1)) + .thenReturn("z-entity") + .thenReturn("a-entity") + .thenReturn("b-entity"); + + List entities = jdbcConn.listEntities("my-domain"); + + // data back is sorted + + assertEquals(3, entities.size()); + assertEquals("a-entity", entities.get(0)); + assertEquals("b-entity", entities.get(1)); + assertEquals("z-entity", entities.get(2)); + jdbcConn.close(); + } + + @Test + public void testGetEntity() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn("{\"value\":1}").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_VALUE); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5); // domain id + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Entity entity = jdbcConn.getEntity("my-domain", "entity1"); + assertNotNull(entity); + assertEquals("entity1", entity.getName()); + assertEquals("{\"value\":1}", JSON.string(entity.getValue())); + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "entity1"); + jdbcConn.close(); + } + + @Test + public void testGetEntityNotFound() throws Exception { + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // for domain id + .thenReturn(false); + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Entity entity = jdbcConn.getEntity("my-domain", "entity1"); + assertNull(entity); + jdbcConn.close(); + } + + @Test + public void testGetEntityDomainNotFound() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + try { + jdbcConn.getEntity("my-domain", "entity1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetEntityException() throws Exception { + + Mockito.when(mockPrepStmt.executeQuery()) + .thenReturn(mockResultSet) + .thenThrow(new SQLException("failed operation", "state", 1001)); + + Mockito.when(mockResultSet.next()) + .thenReturn(true); // for domain id + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5); // domain id + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + try { + jdbcConn.getEntity("my-domain", "entity1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertEntity() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Entity entity = new Entity().setName("entity1").setValue(JSON.fromString("{\"value\":1}", Struct.class)); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + boolean requestSuccess = jdbcConn.insertEntity("my-domain", entity); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "entity1"); + jdbcConn.close(); + } + + @Test + public void testInsertEntityInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Entity entity = new Entity().setName("entity1").setValue(JSON.fromString("{\"value\":1}", Struct.class)); + Mockito.when(mockResultSet.next()).thenReturn(false); + + try { + jdbcConn.insertEntity("my-domain", entity); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertEntityException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Entity entity = new Entity().setName("entity1").setValue(JSON.fromString("{\"value\":1}", Struct.class)); + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.insertEntity("my-domain", entity); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdateEntity() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Entity entity = new Entity().setName("entity1").setValue(JSON.fromString("{\"value\":1}", Struct.class)); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + boolean requestSuccess = jdbcConn.updateEntity("my-domain", entity); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(1, "{\"value\":1}"); + Mockito.verify(mockPrepStmt, times(1)).setInt(2, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "entity1"); + jdbcConn.close(); + } + + @Test + public void testUpdateEntityInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Entity entity = new Entity().setName("entity1").setValue(JSON.fromString("{\"value\":1}", Struct.class)); + Mockito.when(mockResultSet.next()).thenReturn(false); + + try { + jdbcConn.updateEntity("my-domain", entity); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testUpdateEntityException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Entity entity = new Entity().setName("entity1").setValue(JSON.fromString("{\"value\":1}", Struct.class)); + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.updateEntity("my-domain", entity); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteEntity() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + boolean requestSuccess = jdbcConn.deleteEntity("my-domain", "entity1"); + assertTrue(requestSuccess); + + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "entity1"); + jdbcConn.close(); + } + + @Test + public void testDeleteEntityInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()).thenReturn(false); + + try { + jdbcConn.deleteEntity("my-domain", "entity1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testDeleteEntityException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.deleteEntity("my-domain", "entity1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertPrincipalException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.insertPrincipal(mockConn, "domain.user1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertPrincipalZeroAffected() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeUpdate()).thenReturn(0); + int value = jdbcConn.insertPrincipal(mockConn, "domain.user1"); + assertEquals(0, value); + jdbcConn.close(); + } + + @Test + public void testInsertHostException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.insertHost(mockConn, "host1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testInsertHostZeroAffected() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeUpdate()).thenReturn(0); + int value = jdbcConn.insertHost(mockConn, "host1"); + assertEquals(0, value); + jdbcConn.close(); + } + + @Test + public void testListModifiedDomains() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.next()).thenReturn(true) // 3 domains + .thenReturn(true).thenReturn(true).thenReturn(false); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("domain1").thenReturn("domain2").thenReturn("domain3"); // 3 domains + Mockito.doReturn(new java.sql.Timestamp(1454358916)).when(mockResultSet).getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED); + + DomainModifiedList list = jdbcConn.listModifiedDomains(1454358900); + + Mockito.verify(mockPrepStmt, times(1)).setTimestamp(Matchers.eq(1), + Matchers.eq(new java.sql.Timestamp(1454358900)), Matchers.isA(Calendar.class)); + + assertEquals(3, list.getNameModList().size()); + boolean domain1Found = false; + boolean domain2Found = false; + boolean domain3Found = false; + for (DomainModified dom : list.getNameModList()) { + switch (dom.getName()) { + case "domain1": + domain1Found = true; + break; + case "domain2": + domain2Found = true; + break; + case "domain3": + domain3Found = true; + break; + } + } + assertTrue(domain1Found); + assertTrue(domain2Found); + assertTrue(domain3Found); + + jdbcConn.close(); + } + + @Test + public void testListModifiedDomainsNoEntries() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.next()).thenReturn(false); // no entries + + DomainModifiedList list = jdbcConn.listModifiedDomains(1454358900); + assertEquals(0, list.getNameModList().size()); + + jdbcConn.close(); + } + + @Test + public void testListModifiedDomainsException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.listModifiedDomains(1454358900); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetAthenzDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + // one-domain, 2 roles, 2 members altogether + // 2 policies, 2 assertions + // 1 service, 1 public key + Mockito.when(mockResultSet.next()).thenReturn(true) // domain + .thenReturn(true).thenReturn(true).thenReturn(false) // 2 roles + .thenReturn(true).thenReturn(true).thenReturn(false) // 1 member each + .thenReturn(true).thenReturn(true).thenReturn(false) // 2 policies + .thenReturn(true).thenReturn(true).thenReturn(false) // 1 assertion each + .thenReturn(true).thenReturn(false) // 1 service + .thenReturn(true).thenReturn(false) // 1 public key + .thenReturn(true).thenReturn(false); // 1 host + + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("role1").thenReturn("role2") // role names + .thenReturn("policy1").thenReturn("policy2") // policy names + .thenReturn("service1"); // service name + + Mockito.when(mockResultSet.getString(1)) + .thenReturn("role1").thenReturn("role2") // role names + .thenReturn("policy1").thenReturn("policy2") // policy names + .thenReturn("service1"); // service names + + Mockito.when(mockResultSet.getString(2)) + .thenReturn("user").thenReturn("user") // member domain names + .thenReturn("host1"); // service host name + Mockito.when(mockResultSet.getString(3)).thenReturn("user1").thenReturn("user2"); // member local names + + Mockito.doReturn(new java.sql.Timestamp(1454358916)).when(mockResultSet).getTimestamp(ZMSConsts.DB_COLUMN_MODIFIED); + Mockito.doReturn(true).when(mockResultSet).getBoolean(ZMSConsts.DB_COLUMN_ENABLED); + Mockito.doReturn(false).when(mockResultSet).getBoolean(ZMSConsts.DB_COLUMN_AUDIT_ENABLED); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_DESCRIPTION); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_ORG); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_UUID); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_TRUST); + Mockito.doReturn("").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_ACCOUNT); + Mockito.doReturn(0).when(mockResultSet).getInt(ZMSConsts.DB_COLUMN_PRODUCT_ID); + Mockito.doReturn(5).when(mockResultSet).getInt(ZMSConsts.DB_COLUMN_DOMAIN_ID); + Mockito.doReturn("/usr/bin64/athenz").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_EXECTUABLE); + Mockito.doReturn("users").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_SVC_GROUP); + Mockito.doReturn("root").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_SVC_USER); + Mockito.doReturn("http://server.athenzcompany.com").when(mockResultSet).getString(ZMSConsts.DB_COLUMN_PROVIDER_ENDPOINT); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ROLE)) + .thenReturn("role1").thenReturn("role2"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_RESOURCE)) + .thenReturn("my-domain:*").thenReturn("my-domain:service.*"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ACTION)) + .thenReturn("*").thenReturn("read"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_EFFECT)) + .thenReturn("ALLOW").thenReturn("DENY"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_KEY_ID)).thenReturn("zms1.zone1"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_KEY_VALUE)).thenReturn("Value1"); + + AthenzDomain athenzDomain = jdbcConn.getAthenzDomain("my-domain"); + assertNotNull(athenzDomain); + assertEquals("my-domain", athenzDomain.getDomain().getName()); + assertEquals(2, athenzDomain.getRoles().size()); + assertEquals(1, athenzDomain.getRoles().get(0).getMembers().size()); + assertEquals(1, athenzDomain.getRoles().get(1).getMembers().size()); + assertEquals(2, athenzDomain.getPolicies().size()); + assertEquals(1, athenzDomain.getPolicies().get(0).getAssertions().size()); + assertEquals(1, athenzDomain.getPolicies().get(1).getAssertions().size()); + assertEquals(1, athenzDomain.getServices().size()); + assertEquals(1, athenzDomain.getServices().get(0).getPublicKeys().size()); + assertEquals("zms1.zone1", athenzDomain.getServices().get(0).getPublicKeys().get(0).getId()); + assertEquals("Value1", athenzDomain.getServices().get(0).getPublicKeys().get(0).getKey()); + assertEquals(1, athenzDomain.getServices().get(0).getHosts().size()); + assertEquals("host1", athenzDomain.getServices().get(0).getHosts().get(0)); + + jdbcConn.close(); + } + + @Test + public void testSetName() { + AthenzDomain athenzDomain = new AthenzDomain("my-domain"); + try { + athenzDomain.setName("my-domain"); + } catch (Exception ex) { + fail(); + } + assertTrue(true); + } + + @Test + public void testCommit() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, false); + assertFalse(jdbcConn.transactionCompleted); + Mockito.verify(mockConn, times(1)).setAutoCommit(false); + + jdbcConn.commitChanges(); + assertTrue(jdbcConn.transactionCompleted); + Mockito.verify(mockConn, times(1)).commit(); + Mockito.verify(mockConn, times(1)).setAutoCommit(true); + + jdbcConn.close(); + } + + @Test + public void testCommitException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, false); + assertFalse(jdbcConn.transactionCompleted); + Mockito.verify(mockConn, times(1)).setAutoCommit(false); + + Mockito.doThrow(new SQLException("failed operation", "state", 1001)).when(mockConn).commit(); + + try { + jdbcConn.commitChanges(); + fail(); + } catch (ResourceException ex) { + assertTrue(jdbcConn.transactionCompleted); + Mockito.verify(mockConn, times(1)).commit(); + } + + jdbcConn.close(); + } + + @Test + public void testRollback() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, false); + assertFalse(jdbcConn.transactionCompleted); + Mockito.verify(mockConn, times(1)).setAutoCommit(false); + + jdbcConn.rollbackChanges(); + assertTrue(jdbcConn.transactionCompleted); + Mockito.verify(mockConn, times(1)).rollback(); + Mockito.verify(mockConn, times(1)).setAutoCommit(true); + + jdbcConn.close(); + } + + @Test + public void testRollbackException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, false); + assertFalse(jdbcConn.transactionCompleted); + Mockito.verify(mockConn, times(1)).setAutoCommit(false); + + Mockito.doThrow(new SQLException("failed operation", "state", 1001)).when(mockConn).rollback(); + + jdbcConn.rollbackChanges(); + assertTrue(jdbcConn.transactionCompleted); + Mockito.verify(mockConn, times(1)).rollback(); + Mockito.verify(mockConn, times(1)).setAutoCommit(true); + + jdbcConn.close(); + } + + @Test + public void testValidatePrincipalDomainInvalidValue() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, false); + assertFalse(jdbcConn.validatePrincipalDomain("coretech")); + assertFalse(jdbcConn.validatePrincipalDomain(".coretech")); + assertFalse(jdbcConn.validatePrincipalDomain("coretech.")); + assertFalse(jdbcConn.validatePrincipalDomain("coretech.test.")); + jdbcConn.close(); + } + + @Test + public void testValidatePrincipalDomainInvalidDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, false); + Mockito.when(mockResultSet.next()).thenReturn(false); + + assertFalse(jdbcConn.validatePrincipalDomain("coretech.storage")); + assertFalse(jdbcConn.validatePrincipalDomain("coretech.storage.db")); + jdbcConn.close(); + } + + @Test + public void testValidatePrincipalDomain() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, false); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)).thenReturn(5); + + assertTrue(jdbcConn.validatePrincipalDomain("coretech.storage")); + assertTrue(jdbcConn.validatePrincipalDomain("coretech.storage.db")); + assertTrue(jdbcConn.validatePrincipalDomain("user.user1")); + + jdbcConn.close(); + } + + @Test + public void testVerifyDomainAccountUniquenessEmptyAccount() throws Exception { + + // we are going to set the code to return exception so that we can + // verify that we're returning before making any sql calls + + Mockito.when(mockResultSet.next()).thenReturn(false); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.verifyDomainAccountUniqueness("iaas.athenz", null, "unitTest"); + jdbcConn.verifyDomainAccountUniqueness("iaas.athenz", "", "unitTest"); + jdbcConn.close(); + } + + @Test + public void testVerifyDomainAccountUniquenessPass() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn("iaas.athenz").when(mockResultSet).getString(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.verifyDomainAccountUniqueness("iaas.athenz", "12345", "unitTest"); + jdbcConn.close(); + } + + @Test + public void testVerifyDomainAccountUniquenessPassNoMatch() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.verifyDomainAccountUniqueness("iaas.athenz", "12345", "unitTest"); + jdbcConn.close(); + } + + @Test + public void testVerifyDomainAccountUniquenessFail() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn("iaas.athenz.ci").when(mockResultSet).getString(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + try { + jdbcConn.verifyDomainAccountUniqueness("iaas.athenz", "12345", "unitTest"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + assertTrue(ex.getMessage().contains("iaas.athenz.ci")); + } + jdbcConn.close(); + } + + @Test + public void testVerifyDomainProductIdUniquenessEmptyId() throws Exception { + + // we are going to set the code to return exception so that we can + // verify that we're returning before making any sql calls + + Mockito.when(mockResultSet.next()).thenReturn(false); + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.verifyDomainProductIdUniqueness("iaas.athenz", null, "unitTest"); + jdbcConn.verifyDomainProductIdUniqueness("iaas.athenz", Integer.valueOf(0), "unitTest"); + jdbcConn.close(); + } + + @Test + public void testVerifyDomainProductIdUniquenessPass() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn("iaas.athenz").when(mockResultSet).getString(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.verifyDomainProductIdUniqueness("iaas.athenz", Integer.valueOf(1001), "unitTest"); + jdbcConn.close(); + } + + @Test + public void testVerifyDomainProductIdUniquenessPassNoMatch() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(false); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.verifyDomainProductIdUniqueness("iaas.athenz", Integer.valueOf(1001), "unitTest"); + jdbcConn.close(); + } + + @Test + public void testVerifyDomainProductIdUniquenessFail() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn("iaas.athenz.ci").when(mockResultSet).getString(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + try { + jdbcConn.verifyDomainProductIdUniqueness("iaas.athenz", Integer.valueOf(1001), "unitTest"); + fail(); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + assertTrue(ex.getMessage().contains("iaas.athenz.ci")); + } + jdbcConn.close(); + } + + @Test + public void testLookupDomainByAccount() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn("iaas.athenz").when(mockResultSet).getString(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + String domainName = jdbcConn.lookupDomainById("1234", 0); + assertEquals(domainName, "iaas.athenz"); + jdbcConn.close(); + } + + @Test + public void testLookupDomainByProductId() throws Exception { + + Mockito.when(mockResultSet.next()).thenReturn(true).thenReturn(false); + Mockito.doReturn("iaas.athenz").when(mockResultSet).getString(1); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + String domainName = jdbcConn.lookupDomainById(null, Integer.valueOf(1001)); + assertEquals(domainName, "iaas.athenz"); + jdbcConn.close(); + } + + @Test + public void testLookupDomainByRole() throws Exception { + + // 3 domain being returned + + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("zdomain") + .thenReturn("adomain") + .thenReturn("bdomain"); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + List domains = jdbcConn.lookupDomainByRole("user.user", "admin"); + assertEquals(3, domains.size()); + assertEquals("adomain", domains.get(0)); + assertEquals("bdomain", domains.get(1)); + assertEquals("zdomain", domains.get(2)); + jdbcConn.close(); + } + + @Test + public void testLookupDomainByRoleDuplicateDomains() throws Exception { + + // 3 domain being returned but 2 are duplicates + // so our end result must be the unique 2 only + + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("zdomain") + .thenReturn("adomain") + .thenReturn("zdomain"); + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + List domains = jdbcConn.lookupDomainByRole("user.user", "admin"); + assertEquals(2, domains.size()); + assertEquals("adomain", domains.get(0)); + assertEquals("zdomain", domains.get(1)); + jdbcConn.close(); + } + + @Test + public void testListRoleAuditLogsInvalidDomain() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(false); // invalid domain + + try { + jdbcConn.listRoleAuditLogs("my-domain", "role1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testListRoleAuditLogsInvalidRole() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // domain id success + .thenReturn(false); // role id failure + Mockito.doReturn(5).when(mockResultSet).getInt(1); // return domain id + + try { + jdbcConn.listRoleAuditLogs("my-domain", "role1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testListRoleAuditLogsException() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // domain id success + .thenReturn(true); // role id success + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // role id + Mockito.when(mockPrepStmt.executeQuery()) + .thenReturn(mockResultSet) + .thenReturn(mockResultSet) + .thenThrow(new SQLException("failed operation", "state", 1001)); + + try { + jdbcConn.listRoleAuditLogs("my-domain", "role1"); + fail(); + } catch (ResourceException ex) { + assertEquals(500, ex.getCode()); + } + jdbcConn.close(); + } + + @Test + public void testListRoleAuditLogs() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) // domain id success + .thenReturn(true) // role id success + .thenReturn(true) // 2 log entries + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // role id + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ACTION)) + .thenReturn("ADD") + .thenReturn("DELETE"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_MEMBER)) + .thenReturn("user.member1") + .thenReturn("user.member2"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ADMIN)) + .thenReturn("user.admin1") + .thenReturn("user.admin2"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_AUDIT_REF)) + .thenReturn("") + .thenReturn("audit-ref"); + Mockito.doReturn(new java.sql.Timestamp(1454358916)) + .when(mockResultSet).getTimestamp(ZMSConsts.DB_COLUMN_CREATED); + + List logs = jdbcConn.listRoleAuditLogs("my-domain", "role1"); + assertNotNull(logs); + assertEquals(2, logs.size()); + assertEquals("ADD", logs.get(0).getAction()); + assertEquals("user.admin1", logs.get(0).getAdmin()); + assertEquals("user.member1", logs.get(0).getMember()); + assertNull(logs.get(0).getAuditRef()); + assertEquals("DELETE", logs.get(1).getAction()); + assertEquals("user.admin2", logs.get(1).getAdmin()); + assertEquals("user.member2", logs.get(1).getMember()); + assertEquals("audit-ref", logs.get(1).getAuditRef()); + jdbcConn.close(); + } + + @Test + public void testRoleIndex() throws SQLException { + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + assertEquals("101:role1", jdbcConn.roleIndex("101", "role1")); + jdbcConn.close(); + } + + @Test + public void testPrepareRoleAssertionsStatementWithAction() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.prepareRoleAssertionsStatement(mockConn, "create"); + Mockito.verify(mockPrepStmt, times(1)).setString(Matchers.eq(1), Matchers.eq("create")); + jdbcConn.close(); + } + + @Test + public void testPrepareRoleAssertionsStatementEmptyAction() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.prepareRoleAssertionsStatement(mockConn, ""); + jdbcConn.prepareRoleAssertionsStatement(mockConn, null); + Mockito.verify(mockPrepStmt, times(0)).setString(Matchers.isA(Integer.class), Matchers.isA(String.class)); + jdbcConn.close(); + } + + @Test + public void testPrepareRolePrinciaplsStatementWithPrincipal() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.prepareRolePrincipalsStatement(mockConn, "user.user1", false); + Mockito.verify(mockPrepStmt, times(1)).setString(Matchers.eq(1), Matchers.eq("user.user1")); + jdbcConn.close(); + } + + @Test + public void testPrepareRolePrinciaplsStatementEmptyPrincipal() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + jdbcConn.prepareRolePrincipalsStatement(mockConn, "", false); + jdbcConn.prepareRolePrincipalsStatement(mockConn, null, false); + Mockito.verify(mockPrepStmt, times(0)).setString(Matchers.isA(Integer.class), Matchers.isA(String.class)); + jdbcConn.close(); + } + + @Test + public void testGetRoleAssertions() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("dom1") + .thenReturn("dom1") + .thenReturn("dom2"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_DOMAIN_ID)) + .thenReturn("101") + .thenReturn("101") + .thenReturn("102"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ROLE)) + .thenReturn("role1") + .thenReturn("role1") + .thenReturn("role3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_RESOURCE)) + .thenReturn("resource1") + .thenReturn("resource2") + .thenReturn("resource3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ACTION)) + .thenReturn("update"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_EFFECT)) + .thenReturn("ALLOW"); + + Map> roleAssertions = jdbcConn.getRoleAssertions("update", "getRoleAssertions"); + assertEquals(2, roleAssertions.size()); + + List assertions = roleAssertions.get("101:role1"); + assertEquals(2, assertions.size()); + + assertEquals("dom1:role.role1", assertions.get(0).getRole()); + assertEquals("resource1", assertions.get(0).getResource()); + assertEquals("update", assertions.get(0).getAction()); + assertEquals("ALLOW", assertions.get(0).getEffect().toString()); + + assertEquals("dom1:role.role1", assertions.get(1).getRole()); + assertEquals("resource2", assertions.get(1).getResource()); + assertEquals("update", assertions.get(1).getAction()); + assertEquals("ALLOW", assertions.get(1).getEffect().toString()); + + assertions = roleAssertions.get("102:role3"); + assertEquals(1, assertions.size()); + + assertEquals("dom2:role.role3", assertions.get(0).getRole()); + assertEquals("resource3", assertions.get(0).getResource()); + assertEquals("update", assertions.get(0).getAction()); + assertEquals("ALLOW", assertions.get(0).getEffect().toString()); + + jdbcConn.close(); + } + + @Test + public void testGetRolePrincipals() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("user.user1") + .thenReturn("user.user2") + .thenReturn("user.user3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_DOMAIN_ID)) + .thenReturn("101") + .thenReturn("101") + .thenReturn("102"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ROLE_NAME)) + .thenReturn("role1") + .thenReturn("role1") + .thenReturn("role3"); + + Map> rolePrincipals = jdbcConn.getRolePrincipals(null, false, "getRolePrincipals"); + assertEquals(2, rolePrincipals.size()); + + List principals = rolePrincipals.get("101:role1"); + assertEquals(2, principals.size()); + + assertEquals("user.user1", principals.get(0)); + assertEquals("user.user2", principals.get(1)); + + principals = rolePrincipals.get("102:role3"); + assertEquals(1, principals.size()); + + assertEquals("user.user3", principals.get(0)); + + jdbcConn.close(); + } + + @Test + public void testGetTrustedRoles() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("trole1") + .thenReturn("trole2") + .thenReturn("trole3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_DOMAIN_ID)) + .thenReturn("101") + .thenReturn("102") + .thenReturn("103"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ROLE)) + .thenReturn("role1") + .thenReturn("role1") + .thenReturn("role3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ASSERT_DOMAIN_ID)) + .thenReturn("101") + .thenReturn("101") + .thenReturn("103"); + + Map> trustedRoles = jdbcConn.getTrustedRoles("getTrustedRoles"); + assertEquals(2, trustedRoles.size()); + + List roles = trustedRoles.get("101:role1"); + assertEquals(2, roles.size()); + + assertEquals("101:trole1", roles.get(0)); + assertEquals("102:trole2", roles.get(1)); + + roles = trustedRoles.get("103:role3"); + assertEquals(1, roles.size()); + + assertEquals("103:trole3", roles.get(0)); + + jdbcConn.close(); + } + + @Test + public void testGetAwsDomains() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("dom1") + .thenReturn("dom2") + .thenReturn("dom3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ACCOUNT)) + .thenReturn("101") + .thenReturn("102") + .thenReturn("103"); + + Map awsDomains = jdbcConn.getAwsDomains("getAwsDomains"); + assertEquals(3, awsDomains.size()); + + assertEquals("101", awsDomains.get("dom1")); + assertEquals("102", awsDomains.get("dom2")); + assertEquals("103", awsDomains.get("dom3")); + + jdbcConn.close(); + } + + @Test + public void testAddRoleAssertionsEmptyList() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + List principalAssertions = new ArrayList<>(); + + jdbcConn.addRoleAssertions(principalAssertions, null, null); + assertEquals(0, principalAssertions.size()); + + jdbcConn.addRoleAssertions(principalAssertions, new ArrayList(), null); + assertEquals(0, principalAssertions.size()); + + jdbcConn.close(); + } + + @Test + public void testAddRoleAssertionsAwsDomainListEmpty() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + List principalAssertions = new ArrayList<>(); + + List roleAssertions = new ArrayList<>(); + Assertion assertion = new Assertion().setAction("update").setResource("dom1:resource").setRole("role"); + roleAssertions.add(assertion); + + jdbcConn.addRoleAssertions(principalAssertions, roleAssertions, null); + assertEquals(1, principalAssertions.size()); + + principalAssertions.clear(); + jdbcConn.addRoleAssertions(principalAssertions, roleAssertions, new HashMap()); + assertEquals(1, principalAssertions.size()); + + jdbcConn.close(); + } + + @Test + public void testAddRoleAssertions() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + List principalAssertions = new ArrayList<>(); + + List roleAssertions = new ArrayList<>(); + Assertion assertion = new Assertion().setAction("update").setResource("dom1:resource").setRole("role"); + roleAssertions.add(assertion); + + assertion = new Assertion().setAction("update").setResource("dom2:resource1").setRole("role"); + roleAssertions.add(assertion); + + assertion = new Assertion().setAction("update").setResource("resource3").setRole("role"); + roleAssertions.add(assertion); + + Map awsDomains = new HashMap<>(); + awsDomains.put("dom1", "12345"); + + jdbcConn.addRoleAssertions(principalAssertions, roleAssertions, awsDomains); + assertEquals(3, principalAssertions.size()); + + assertEquals("arn:aws:iam::12345:role/resource", principalAssertions.get(0).getResource()); + assertEquals("dom2:resource1", principalAssertions.get(1).getResource()); + assertEquals("resource3", principalAssertions.get(2).getResource()); + + jdbcConn.close(); + } + + @Test + public void testSqlError() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + SQLException ex = new SQLException("sql-reason", "08S01", 9999); + ResourceException rEx = (ResourceException) jdbcConn.sqlError(ex, "sqlError"); + assertEquals(ResourceException.CONFLICT, rEx.getCode()); + + ex = new SQLException("sql-reason", "40001", 9999); + rEx = (ResourceException) jdbcConn.sqlError(ex, "sqlError"); + assertEquals(ResourceException.CONFLICT, rEx.getCode()); + + ex = new SQLException("sql-reason", "sql-state", 1290); + rEx = (ResourceException) jdbcConn.sqlError(ex, "sqlError"); + assertEquals(ResourceException.GONE, rEx.getCode()); + + ex = new SQLException("sql-reason", "sql-state", 1062); + rEx = (ResourceException) jdbcConn.sqlError(ex, "sqlError"); + assertEquals(ResourceException.BAD_REQUEST, rEx.getCode()); + jdbcConn.close(); + } + + @Test + public void testListResourceAccessNotRegisteredRolePrincipals() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + // no role principals + + Mockito.when(mockResultSet.next()) + .thenReturn(false); + + // we must get back 404 since the user doesn't exist in system + + try { + jdbcConn.listResourceAccess("user.user1", "update", "user"); + fail(); + } catch (ResourceException ex) { + assertEquals(ResourceException.NOT_FOUND, ex.getCode()); + } + + jdbcConn.close(); + } + + @Test + public void testListResourceAccessRegisteredRolePrincipals() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + // no role principals + + Mockito.when(mockResultSet.next()) + .thenReturn(false) // no role principal return + .thenReturn(true); // valid principal id + Mockito.doReturn(7).when(mockResultSet).getInt(1); + + ResourceAccessList resourceAccessList = jdbcConn.listResourceAccess("user.user1", "update", "user"); + + // we should get an empty assertion set for the principal + + List resources = resourceAccessList.getResources(); + assertEquals(1, resources.size()); + ResourceAccess rsrcAccess = resources.get(0); + assertEquals("user.user1", rsrcAccess.getPrincipal()); + List assertions = rsrcAccess.getAssertions(); + assertTrue(assertions.isEmpty()); + + jdbcConn.close(); + } + + @Test + public void testListResourceAccessEmptyRoleAssertions() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false) // upto here is role principals + .thenReturn(false); // we have no role assertions + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("user.user1") + .thenReturn("user.user2") + .thenReturn("user.user3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_DOMAIN_ID)) + .thenReturn("101") + .thenReturn("101") + .thenReturn("102"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ROLE_NAME)) + .thenReturn("role1") + .thenReturn("role1") + .thenReturn("role3"); + + ResourceAccessList resourceAccessList = jdbcConn.listResourceAccess("user.user1", "update", "user"); + + // we should get an empty assertion set for the principal + + List resources = resourceAccessList.getResources(); + assertEquals(1, resources.size()); + ResourceAccess rsrcAccess = resources.get(0); + assertEquals("user.user1", rsrcAccess.getPrincipal()); + List assertions = rsrcAccess.getAssertions(); + assertTrue(assertions.isEmpty()); + + jdbcConn.close(); + } + + @Test + public void testListResourceAccess() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false) // up to here is role principals + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false) // up to here is role assertions + .thenReturn(false); // no trusted role + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("user.user1") + .thenReturn("user.user2") + .thenReturn("user.user3") // up to here is role principals + .thenReturn("dom1") + .thenReturn("dom1") + .thenReturn("dom2"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_DOMAIN_ID)) + .thenReturn("101") + .thenReturn("101") + .thenReturn("102") // up to here is role principals + .thenReturn("101") + .thenReturn("101") + .thenReturn("102"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ROLE_NAME)) + .thenReturn("role1") + .thenReturn("role1") + .thenReturn("role3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ROLE)) + .thenReturn("role1") + .thenReturn("role1") + .thenReturn("role3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_RESOURCE)) + .thenReturn("resource1") + .thenReturn("resource2") + .thenReturn("resource3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ACTION)) + .thenReturn("update"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_EFFECT)) + .thenReturn("ALLOW"); + + ResourceAccessList resourceAccessList = jdbcConn.listResourceAccess(null, "update", "user"); + List resources = resourceAccessList.getResources(); + assertEquals(3, resources.size()); + + boolean userUser1 = false; + boolean userUser2 = false; + boolean userUser3 = false; + for (ResourceAccess rsrcAccess : resources) { + + switch (rsrcAccess.getPrincipal()) { + case "user.user1": + userUser1 = true; + assertEquals(2, rsrcAccess.getAssertions().size()); + break; + case "user.user2": + userUser2 = true; + assertEquals(2, rsrcAccess.getAssertions().size()); + break; + case "user.user3": + userUser3 = true; + assertEquals(1, rsrcAccess.getAssertions().size()); + break; + } + } + assertTrue(userUser1); + assertTrue(userUser2); + assertTrue(userUser3); + jdbcConn.close(); + } + + @Test + public void testListResourceAccessAws() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false) // up to here is role principals + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false) // up to here is role assertions + .thenReturn(true) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false) // up to here trusted roles + .thenReturn(true) + .thenReturn(false); // up to here is aws domains + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_NAME)) + .thenReturn("user.user1") + .thenReturn("user.user2") + .thenReturn("user.user3.service") // up to here is role principals + .thenReturn("dom1") + .thenReturn("dom2") + .thenReturn("dom3") // up to here is role assertions + .thenReturn("trole1") + .thenReturn("trole2") + .thenReturn("trole3") // up to here trusted roles + .thenReturn("dom1"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_DOMAIN_ID)) + .thenReturn("101") + .thenReturn("102") + .thenReturn("103") // up to here is role principals + .thenReturn("101") + .thenReturn("102") + .thenReturn("103") // up to here role assertions + .thenReturn("101") + .thenReturn("102") + .thenReturn("103"); // up to here trusted roles + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ROLE_NAME)) + .thenReturn("role1") + .thenReturn("role2") + .thenReturn("role3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ROLE)) + .thenReturn("role1") + .thenReturn("role2") + .thenReturn("role3") // up to here role assertions + .thenReturn("role1") + .thenReturn("role2") + .thenReturn("role3"); // up to here trusted roles + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_RESOURCE)) + .thenReturn("dom1:role1") + .thenReturn("dom2:role2") + .thenReturn("resource3"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ACTION)) + .thenReturn("assume_aws_role"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_EFFECT)) + .thenReturn("ALLOW"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ACCOUNT)) + .thenReturn("12345"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ASSERT_DOMAIN_ID)) + .thenReturn("101") + .thenReturn("102") + .thenReturn("103"); + + ResourceAccessList resourceAccessList = jdbcConn.listResourceAccess(null, "assume_aws_role", "user"); + List resources = resourceAccessList.getResources(); + assertEquals(2, resources.size()); + + boolean userUser1 = false; + boolean userUser2 = false; + boolean userUser3 = false; // must be skipped + for (ResourceAccess rsrcAccess : resources) { + + switch (rsrcAccess.getPrincipal()) { + case "user.user1": + userUser1 = true; + assertEquals(1, rsrcAccess.getAssertions().size()); + assertEquals("arn:aws:iam::12345:role/role1", rsrcAccess.getAssertions().get(0).getResource()); + break; + case "user.user2": + userUser2 = true; + assertEquals(1, rsrcAccess.getAssertions().size()); + assertEquals("dom2:role2", rsrcAccess.getAssertions().get(0).getResource()); + break; + case "user.user3.service": + userUser3 = true; + break; + } + } + assertTrue(userUser1); + assertTrue(userUser2); + assertFalse(userUser3); + jdbcConn.close(); + } + + @Test + public void testGetResourceAccessObject() throws SQLException { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + ResourceAccess rsrcAccess = jdbcConn.getResourceAccessObject("user.user1", null); + assertEquals("user.user1", rsrcAccess.getPrincipal()); + List assertions = rsrcAccess.getAssertions(); + assertTrue(assertions.isEmpty()); + + List roleAssertions = new ArrayList<>(); + Assertion assertion = new Assertion().setAction("update").setRole("role").setResource("resource"); + roleAssertions.add(assertion); + + rsrcAccess = jdbcConn.getResourceAccessObject("user.user2", roleAssertions); + assertEquals("user.user2", rsrcAccess.getPrincipal()); + assertions = rsrcAccess.getAssertions(); + assertEquals(1, assertions.size()); + Assertion testAssertion = assertions.get(0); + assertEquals("update", testAssertion.getAction()); + assertEquals("role", testAssertion.getRole()); + assertEquals("resource", testAssertion.getResource()); + + jdbcConn.close(); + } + + @Test + public void testUpdatePolicyModTimestampSuccess() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(1).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // policy id + + boolean requestSuccess = jdbcConn.updatePolicyModTimestamp("my-domain", "policy1"); + assertTrue(requestSuccess); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + // get policy id + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "policy1"); + // update policy time-stamp + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + jdbcConn.close(); + } + + @Test + public void testUpdatePolicyModTimestampFailure() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.doReturn(0).when(mockPrepStmt).executeUpdate(); + Mockito.when(mockResultSet.next()).thenReturn(true); + Mockito.when(mockResultSet.getInt(1)) + .thenReturn(5) // domain id + .thenReturn(7); // policy id + + boolean requestSuccess = jdbcConn.updatePolicyModTimestamp("my-domain", "policy1"); + assertFalse(requestSuccess); + + // get domain id + Mockito.verify(mockPrepStmt, times(1)).setString(1, "my-domain"); + // get policy id + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 5); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "policy1"); + // update policy time-stamp + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 7); + jdbcConn.close(); + } + + @Test + public void testUpdatePolicyModTimestampException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeUpdate()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.updatePolicyModTimestamp("my-domain", "policy1"); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + + @Test + public void testGetAssertion() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockResultSet.next()) + .thenReturn(true); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ROLE)) + .thenReturn("role1"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_RESOURCE)) + .thenReturn("my-domain:*"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_ACTION)) + .thenReturn("*"); + Mockito.when(mockResultSet.getString(ZMSConsts.DB_COLUMN_EFFECT)) + .thenReturn("ALLOW"); + + Assertion assertion = jdbcConn.getAssertion("my-domain", "policy1", Long.valueOf(101)); + + assertEquals("my-domain:role.role1", assertion.getRole()); + assertEquals("my-domain:*", assertion.getResource()); + assertEquals("*", assertion.getAction()); + assertEquals("ALLOW", assertion.getEffect().toString()); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 101); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "policy1"); + + jdbcConn.close(); + } + + @Test + public void testGetAssertionNoMatch() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + Mockito.when(mockResultSet.next()) + .thenReturn(false); + + Assertion assertion = jdbcConn.getAssertion("my-domain", "policy1", Long.valueOf(101)); + assertNull(assertion); + + Mockito.verify(mockPrepStmt, times(1)).setInt(1, 101); + Mockito.verify(mockPrepStmt, times(1)).setString(2, "my-domain"); + Mockito.verify(mockPrepStmt, times(1)).setString(3, "policy1"); + + jdbcConn.close(); + } + + @Test + public void testGetAssertionException() throws Exception { + + JDBCConnection jdbcConn = new JDBCConnection(mockConn, true); + + Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("failed operation", "state", 1001)); + try { + jdbcConn.getAssertion("my-domain", "policy1", Long.valueOf(101)); + fail(); + } catch (Exception ex) { + assertTrue(true); + } + jdbcConn.close(); + } + +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/JDBCObjectStoreTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/JDBCObjectStoreTest.java new file mode 100644 index 00000000000..d5b84657ed5 --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/store/jdbc/JDBCObjectStoreTest.java @@ -0,0 +1,53 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.store.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import com.yahoo.athenz.zms.store.jdbc.JDBCObjectStore; +import com.yahoo.athenz.zms.store.jdbc.PoolableDataSource; + +import static org.testng.Assert.*; + +public class JDBCObjectStoreTest { + + @Test + public void testGetConnection() throws SQLException { + PoolableDataSource mockDataSrc = Mockito.mock(PoolableDataSource.class); + Connection mockConn = Mockito.mock(Connection.class); + Mockito.doReturn(mockConn).when(mockDataSrc).getConnection(); + JDBCObjectStore store = new JDBCObjectStore(mockDataSrc); + assertNotNull(store.getConnection(true)); + store.clearConnections(); + } + + @Test + public void testGetConnectionException() throws SQLException { + PoolableDataSource mockDataSrc = Mockito.mock(PoolableDataSource.class); + Mockito.doThrow(new SQLException()).when(mockDataSrc).getConnection(); + try { + JDBCObjectStore store = new JDBCObjectStore(mockDataSrc); + store.getConnection(true); + fail(); + } catch (RuntimeException ex) { + assertTrue(true); + } + } +} diff --git a/servers/zms/src/test/java/com/yahoo/athenz/zms/utils/ZMSUtilsTest.java b/servers/zms/src/test/java/com/yahoo/athenz/zms/utils/ZMSUtilsTest.java new file mode 100644 index 00000000000..0a0d220896c --- /dev/null +++ b/servers/zms/src/test/java/com/yahoo/athenz/zms/utils/ZMSUtilsTest.java @@ -0,0 +1,147 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zms.utils; + +import static org.testng.Assert.*; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.AssertionEffect; +import com.yahoo.athenz.zms.utils.ZMSUtils; + +public class ZMSUtilsTest { + + @Test + public void testRemoveDomainPrefix() { + assertEquals("role1", ZMSUtils.removeDomainPrefix("role1", "domain1", "role.")); + assertEquals("role1", ZMSUtils.removeDomainPrefix("domain1:role.role1", "domain1", "role.")); + assertEquals("domain1:role.role1", ZMSUtils.removeDomainPrefix("domain1:role.role1", "domain2", "role.")); + assertEquals("domain1:role.role1", ZMSUtils.removeDomainPrefix("domain1:role.role1", "domain1", "policy.")); + assertEquals("policy1", ZMSUtils.removeDomainPrefix("domain1:policy.policy1", "domain1", "policy.")); + } + + @Test + public void testGetTenantResourceGroupRolePrefix() { + + assertEquals("storage.tenant.sports.api.", + ZMSUtils.getTenantResourceGroupRolePrefix("storage", "sports.api", null)); + assertEquals("storage.tenant.sports.api.res_group.Group1.", + ZMSUtils.getTenantResourceGroupRolePrefix("storage", "sports.api", "Group1")); + } + + @Test + public void testGetTrustedResourceGroupRolePrefix() { + assertEquals("coretech:role.storage.tenant.sports.api.", + ZMSUtils.getTrustedResourceGroupRolePrefix("coretech", "storage", "sports.api", null)); + assertEquals("coretech:role.storage.tenant.sports.api.res_group.group1.", + ZMSUtils.getTrustedResourceGroupRolePrefix("coretech", "storage", "sports.api", "group1")); + } + + @Test + public void testGetProviderResourceGroupRolePrefix() { + assertEquals("sports.hosted.res_group.hockey.", + ZMSUtils.getProviderResourceGroupRolePrefix("sports", "hosted", "hockey")); + assertEquals("sports.hosted.", + ZMSUtils.getProviderResourceGroupRolePrefix("sports", "hosted", null)); + } + + @Test + public void testAssumeRoleResourceMatchActionNoMatch() { + Assertion assertion = new Assertion() + .setAction("test") + .setEffect(AssertionEffect.ALLOW) + .setRole("domain1:role.role1") + .setResource("domain1:*"); + assertFalse(ZMSUtils.assumeRoleResourceMatch("domain1:role.role1", assertion)); + + assertion = new Assertion() + .setAction("assume_role1") + .setEffect(AssertionEffect.ALLOW) + .setRole("domain1:role.role1") + .setResource("domain1:*"); + assertFalse(ZMSUtils.assumeRoleResourceMatch("domain1:role.role1", assertion)); + + assertion = new Assertion() + .setAction("assume_rol") + .setEffect(AssertionEffect.ALLOW) + .setRole("domain1:role.role1") + .setResource("domain1:*"); + assertFalse(ZMSUtils.assumeRoleResourceMatch("domain1:role.role1", assertion)); + } + + @Test + public void testAssumeRoleResourceMatchRoleNoMatch() { + Assertion assertion = new Assertion() + .setAction("assume_role") + .setEffect(AssertionEffect.ALLOW) + .setRole("domain1:role.role1") + .setResource("domain1:role.role2"); + assertFalse(ZMSUtils.assumeRoleResourceMatch("domain1:role.role1", assertion)); + + assertion = new Assertion() + .setAction("assume_role") + .setEffect(AssertionEffect.ALLOW) + .setRole("domain1:role.role1") + .setResource("domain2:role.role1"); + assertFalse(ZMSUtils.assumeRoleResourceMatch("domain1:role.role1", assertion)); + + assertion = new Assertion() + .setAction("assume_role") + .setEffect(AssertionEffect.ALLOW) + .setRole("domain1:role.role1") + .setResource("domain1:role.reader*"); + assertFalse(ZMSUtils.assumeRoleResourceMatch("domain1:role.role1", assertion)); + + assertion = new Assertion() + .setAction("assume_role") + .setEffect(AssertionEffect.ALLOW) + .setRole("domain1:role.role1") + .setResource("*:role.role2"); + assertFalse(ZMSUtils.assumeRoleResourceMatch("domain1:role.role1", assertion)); + } + + @Test + public void testAssumeRoleResourceMatch() { + Assertion assertion = new Assertion() + .setAction("assume_role") + .setEffect(AssertionEffect.ALLOW) + .setRole("domain2:role.role1") + .setResource("domain1:role.role1"); + assertTrue(ZMSUtils.assumeRoleResourceMatch("domain1:role.role1", assertion)); + + assertion = new Assertion() + .setAction("assume_role") + .setEffect(AssertionEffect.ALLOW) + .setRole("domain2:role.role1") + .setResource("domain1:role.*"); + assertTrue(ZMSUtils.assumeRoleResourceMatch("domain1:role.role1", assertion)); + + assertion = new Assertion() + .setAction("assume_role") + .setEffect(AssertionEffect.ALLOW) + .setRole("domain2:role.role1") + .setResource("domain1:*"); + assertTrue(ZMSUtils.assumeRoleResourceMatch("domain1:role.role1", assertion)); + + assertion = new Assertion() + .setAction("assume_role") + .setEffect(AssertionEffect.ALLOW) + .setRole("domain2:role.role1") + .setResource("*:role.role1"); + assertTrue(ZMSUtils.assumeRoleResourceMatch("domain1:role.role1", assertion)); + } +} diff --git a/servers/zms/src/test/resources/authorized_services.json b/servers/zms/src/test/resources/authorized_services.json new file mode 100644 index 00000000000..5ad9c851930 --- /dev/null +++ b/servers/zms/src/test/resources/authorized_services.json @@ -0,0 +1,38 @@ +{ + "services" : { + "coretech.storage": { + "allowedOperations": [ + {"name":"putproviderresourcegrouproles"}, + {"name":"deleteproviderresourcegrouproles"}, + {"name":"putrole"}, + {"name":"putpolicy"}, + {"name":"puttenancy"}, + {"name":"deletetenancy"} + ] + }, + "coretech.index": { + "allowedOperations": [ + {"name":"putrole"}, + {"name":"putpolicy"} + ] + }, + "sports.hockey": { + }, + "coretech.newsvc": { + "allowedOperations": [ + {"name":"putrole"}, + {"name":"putmembership", + "items": { + "role" : [ + "platforms_deployer", + "platforms_different_deployer" + ], + "not_role" : [ + "platforms_role_deployer" + ] + } + } + ] + } + } +} diff --git a/servers/zms/src/test/resources/logback.xml b/servers/zms/src/test/resources/logback.xml new file mode 100644 index 00000000000..9297d1db86d --- /dev/null +++ b/servers/zms/src/test/resources/logback.xml @@ -0,0 +1,58 @@ + + + + + + + System.out + + %-4relative [%thread] %-5level %class - %msg%n + + + + + + + ${LOG_DIR}/server.log + true + + + ${LOG_DIR}/server.%d{yyyy-MM-dd}.log + 7 + true + + + + %-4relative [%thread] %-5level %logger{35} - %msg%n + + + + + + + ${LOG_DIR}/audit.log + true + + + ${LOG_DIR}/audit.%d{yyyy-MM-dd}.log + 7 + true + + + + %-4relative [%thread] %-5level %logger{35} - %msg%n + + + + + + + + + + + + + + + diff --git a/servers/zms/src/test/resources/solution_templates.json b/servers/zms/src/test/resources/solution_templates.json new file mode 100644 index 00000000000..007930f95d9 --- /dev/null +++ b/servers/zms/src/test/resources/solution_templates.json @@ -0,0 +1,234 @@ +{ + "templates" : { + "vipng": { + "roles": [ + { + "name": "_domain_:role.vip_admin", + "modified": "1970-01-01T00:00:00.000Z" + }, + { + "name": "_domain_:role.sys_network_super_vip_admin", + "modified": "1970-01-01T00:00:00.000Z", + "trust": "sys.network" + } + ], + "policies": [ + { + "name": "_domain_:policy.vip_admin", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:vip*", + "role": "_domain_:role.vip_admin", + "action": "*" + } + ] + }, + { + "name": "_domain_:policy.sys_network_super_vip_admin", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:vip*", + "role": "_domain_:role.sys_network_super_vip_admin", + "action": "*" + } + ] + } + ] + }, + "platforms": { + "roles": [ + { + "name": "_domain_:role.platforms_deployer", + "modified": "1970-01-01T00:00:00.000Z" + } + ], + "policies": [ + { + "name": "_domain_:policy.platforms_deploy", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:service", + "role": "_domain_:role.platforms_deployer", + "action": "*" + }, + { + "resource": "_domain_:service.*", + "role": "_domain_:role.platforms_deployer", + "action": "update" + }, + { + "resource": "_domain_:identity", + "role": "_domain_:role.platforms_deployer", + "action": "create" + }, + { + "resource": "_domain_:identity.*", + "role": "_domain_:role.platforms_deployer", + "action": "read" + }, + { + "resource": "_domain_:daemon.*", + "role": "_domain_:role.platforms_deployer", + "action": "update" + }, + { + "resource": "_domain_:ruleset.*", + "role": "_domain_:role.platforms_deployer", + "action": "update" + }, + { + "resource": "ops_tools.dnsdb:role._domain__traffic_user", + "role": "_domain_:role.platforms_deployer", + "action": "assume_role" + } + ] + } + ] + }, + "user_provisioning": { + "roles": [ + { + "name": "_domain_:role.user", + "modified": "1970-01-01T00:00:00.000Z" + }, + { + "name": "_domain_:role.superuser", + "modified": "1970-01-01T00:00:00.000Z" + }, + { + "name": "_domain_:role.openstack_readers", + "members": [ "sys.builder", "sys.openstack" ], + "modified": "1970-01-01T00:00:00.000Z" + } + ], + "policies": [ + { + "name": "_domain_:policy.user", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:node.*", + "role": "_domain_:role.user", + "action": "node_user" + } + ] + }, + { + "name": "_domain_:policy.superuser", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:node.*", + "role": "_domain_:role.superuser", + "action": "node_sudo" + } + ] + }, + { + "name": "_domain_:policy.openstack_readers", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:nodedef", + "role": "_domain_:role.openstack_readers", + "action": "read" + }, + { + "resource": "_domain_:node", + "role": "_domain_:role.openstack_readers", + "action": "delete" + } + ] + } + ] + }, + "sports": { + "roles": [ + { + "name": "_domain_:role.tenancy.sports.se.access", + "modified": "1970-01-01T00:00:00.000Z", + "trust": "sports" + } + ], + "policies": [ + { + "name": "_domain_:policy.tenancy.sports.se.access", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:tenancy.sports.*", + "role": "_domain_:role.tenancy.sports.se.access", + "action": "update" + } + ] + } + ] + }, + "network": { + "roles": [ + { + "name": "_domain_:role.network_manager", + "modified": "1970-01-01T00:00:00.000Z" + }, + { + "name": "_domain_:role.sys_security_network_admin", + "modified": "1970-01-01T00:00:00.000Z", + "trust": "sys.security" + }, + { + "name": "_domain_:role.sys_security_network_manager_global", + "modified": "1970-01-01T00:00:00.000Z", + "trust": "sys.security" + } + ], + "policies": [ + { + "name": "_domain_:policy.network_manager", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:acl*", + "role": "_domain_:role.network_manager", + "action": "*" + } + ] + }, + { + "name": "_domain_:policy.sys_security_network_admin", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:acl*", + "role": "_domain_:role.sys_security_network_admin", + "action": "*" + } + ] + }, + { + "name": "_domain_:policy.sys_security_network_manager_global", + "modified": "1970-01-01T00:00:00.000Z", + "assertions": [ + { + "resource": "_domain_:acl*", + "role": "_domain_:role.sys_security_network_manager_global", + "action": "create" + }, + { + "resource": "_domain_:acl*", + "role": "_domain_:role.sys_security_network_manager_global", + "action": "update" + }, + { + "resource": "_domain_:acl*", + "role": "_domain_:role.sys_security_network_manager_global", + "action": "delete" + } + ] + } + ] + } + } +} diff --git a/servers/zms/src/test/resources/zms_private.pem b/servers/zms/src/test/resources/zms_private.pem new file mode 100644 index 00000000000..356e1edf8bd --- /dev/null +++ b/servers/zms/src/test/resources/zms_private.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTqa+RwOH3ZcMKyoGxOJo9A +yeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQJARpQBv09w/j6O7TmgtJm4 +Ws5bIxMgOkrHaqPs2Epq8rX8FUyaYSHBL4yYhqWlno4j1TrLzWneni8kJCUNJydU +AQIhAPXBXPQ3UzzNeQC5gbcweMMIQOYHyF7W/NAEILSKgy6hAiEAxL7hOjpdYGtj +hjG1nVIs+HW6z5TnRig7JjTwqA8RMdECIQC4oPCIuRfb0jJaDQQa8FuJiqXXK3mp +ZrLARJmdiYJMgQIgZxpAnWsIlAay2RgjvJXbyzim9TFrIXDjzlnf47JBqIECIBue +3ht9BnawOQqGzFjD89dFQ6nZtbiAaqORj276/SzZ +-----END RSA PRIVATE KEY----- diff --git a/servers/zms/src/test/resources/zms_private_k1.pem b/servers/zms/src/test/resources/zms_private_k1.pem new file mode 100644 index 00000000000..71ef39207a4 --- /dev/null +++ b/servers/zms/src/test/resources/zms_private_k1.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC1ZXd4eOeKlbvmY9ffJ7CYWuVwC6BZswpAinrCMohee6CZgqeF +75T0PN0jb8qZRLaAMV8kBl08NWoW1qwGl/TRz5dJPwWiij+NDlyw61ifouArrayb +Hgd+cHDb5O/HBFzNA9ejImVf5BZjUNuYpgqYU53finp5VKGXsL5bIilt7wIDAQAB +AoGBAJv3IzLtWb/5oHamcCzKNne4579f775QR8dYidZBlqkOSPbUtO9fIMajMkxO +yXbg8r3kNXaHFlE3aAE9EOoWa4Mr+2ibf9EFpsud1n9dU7ZW1Q+vayrjOdqy4fsv +Ppp2Q1g4hxJaqPppPP1yjHYOoAifxVN3pcBoryuZZEE5DjIxAkEA3JhEa22AVeLm +4vWJRmjFaOC0eotyVBFnU5oM3Qh0YeDW6VQlFYSFriQte2/Y3HD5ufY3XgGAj5hn +tbdGTcztFwJBANKCoCybCMd1sIU6mYAGFVt17yBvbmfj5mXk0uxdPHOHB9z5zyxj +S0K4NIlUI/CpT3Ed7mo54sPwOMKGjci1/OkCQQDJWSaiU8mfG0OZh3psTPdbWjcB +38RUHaqNcYAqRCxOprYjNU3ADT0jjRwCnj14QJqAdc9lu8NHzlljUM0X+GOzAkAX +VfWtnC4X9lrwGfPkNkht0CAHB3NEvwXOa9RZ8Wf/Iovjmim2MPep2MOyKYqmZg5x +arEAQ5R4xgt0vPfAtTPpAkAD46oZlxxxcONlqrmGAbHZJTtS5cYxAgZT2lt6YyTj ++k/KFQkCuVaVmTtkwqUWmxsKSgY7uZnSe/BmSZRA03uN +-----END RSA PRIVATE KEY----- diff --git a/servers/zms/src/test/resources/zms_private_k2.pem b/servers/zms/src/test/resources/zms_private_k2.pem new file mode 100644 index 00000000000..4bc8a8c0da9 --- /dev/null +++ b/servers/zms/src/test/resources/zms_private_k2.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDP9IwR0+n3oeQRKSf43r97j1j6cUSbDDdrvfBAzSRSTyYJteGH +Wu3ty2gJnnYEDBxj6n5z+RZrcpjFS6wjxvQdl2eg6dYgBB2+9QkM6UgYs/LA1mgf +DtLXT+bEskZ+qeXYTTWGvXkPFREaNcabFk71pODVUHMj/+Tf2YLdWf+S6QIDAQAB +AoGALyln5tcnlaiaf6XU0eU6ifwtksbDZ2Jy4zV1Wkzr8trOZp9gIYFmumpKEvxk +CXDxs1tICGcyRXX83anWxVpmnDLA+FBqxJwJC5075oLxCFDE78VlXrWNfnAuhxh3 +ehZ+du4FnVru60M7Wrfk2H9hY0J4m0rPE+ueBG6Q0Mu+a+0CQQDq9FfS4su+sDPv +1BP01roMoFywA7o2MUcM55m2S3anFIAJT4NiifFioy/Vjn8TYKDkRpY2f/fWH+hq +8/ynyx0HAkEA4pUYUmgBA41YNG0cHJmMgdr30phW8qLE9JgCO4KOpr7+c3Y0rN0h +e9GXcCirhIscI7qUeTWXxECZcqLMtOfEjwJAFvveF4AeJEJCYmShqyLSQmfxiTpk +HecGJ0oErGOHcOK9f6uqk5og5eBGzqJI5hFey9Xn3d741JZ8evHxNkzVQwJAdTlR +tgCc1augwK7aZmmCagRRRqEOCsXvQ+QI86TeDKKvnii/o9db5WVQBfTkl9QooMt8 +2SDC/gO137seUVG/PQJAKjHW6X7gQAAZDndXJ93lRAYHRiMjQWEZ/yx1Z0NMoRNd +08JtxGD9FdnIXIHJ2UrQKaZPhKTEw/4orTHrurY0bA== +-----END RSA PRIVATE KEY----- diff --git a/servers/zms/src/test/resources/zms_public.pem b/servers/zms/src/test/resources/zms_public.pem new file mode 100644 index 00000000000..727c3c67f22 --- /dev/null +++ b/servers/zms/src/test/resources/zms_public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTq +a+RwOH3ZcMKyoGxOJo9AyeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQ== +-----END PUBLIC KEY----- diff --git a/servers/zms/src/test/resources/zms_public_k1.pem b/servers/zms/src/test/resources/zms_public_k1.pem new file mode 100644 index 00000000000..6843735dbd5 --- /dev/null +++ b/servers/zms/src/test/resources/zms_public_k1.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1ZXd4eOeKlbvmY9ffJ7CYWuVw +C6BZswpAinrCMohee6CZgqeF75T0PN0jb8qZRLaAMV8kBl08NWoW1qwGl/TRz5dJ +PwWiij+NDlyw61ifouArraybHgd+cHDb5O/HBFzNA9ejImVf5BZjUNuYpgqYU53f +inp5VKGXsL5bIilt7wIDAQAB +-----END PUBLIC KEY----- diff --git a/servers/zms/src/test/resources/zms_public_k2.pem b/servers/zms/src/test/resources/zms_public_k2.pem new file mode 100644 index 00000000000..333b8a2711a --- /dev/null +++ b/servers/zms/src/test/resources/zms_public_k2.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDP9IwR0+n3oeQRKSf43r97j1j6 +cUSbDDdrvfBAzSRSTyYJteGHWu3ty2gJnnYEDBxj6n5z+RZrcpjFS6wjxvQdl2eg +6dYgBB2+9QkM6UgYs/LA1mgfDtLXT+bEskZ+qeXYTTWGvXkPFREaNcabFk71pODV +UHMj/+Tf2YLdWf+S6QIDAQAB +-----END PUBLIC KEY----- diff --git a/servers/zts/README.md b/servers/zts/README.md new file mode 100644 index 00000000000..a7aab3dd1bb --- /dev/null +++ b/servers/zts/README.md @@ -0,0 +1,12 @@ +ZTS Server +======================= + +ZTS (AuthZ Token System) + +ZTS, the authentication token service, is only needed to support decentralized or data plane functionality. In many ways, ZTS is like a local replica of ZMS’s data to check a principal’s authentication and confirm membership in roles within a domain. The authentication is in the form of a signed ZToken that can be presented to any decentralized service that wants to authorize access efficiently. If needed, multiple ZTS instances will be distributed to different data centers as needed to scale for issuing tokens. + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/servers/zts/conf/aws_public.crt b/servers/zts/conf/aws_public.crt new file mode 100644 index 00000000000..7f8173633c7 --- /dev/null +++ b/servers/zts/conf/aws_public.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw +FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD +VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z +ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u +IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl +cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e +ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3 +VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P +hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j +k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U +hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF +lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf +MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW +MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw +vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw +7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K + -----END CERTIFICATE----- diff --git a/servers/zts/conf/container_settings b/servers/zts/conf/container_settings new file mode 100644 index 00000000000..ccef22a5056 --- /dev/null +++ b/servers/zts/conf/container_settings @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +# list of authorities for authenticating principals +CONTAINER_AUTHORITY_CLASSES="com.yahoo.athenz.auth.impl.PrincipalAuthority,com.yahoo.athenz.auth.impl.UserAuthority" + +# private/public key pair for zts instance - must be generated +# using with default key id of 0 +CONTAINER_PRIVKEY="${ROOT}/var/zts_server/keys/zts_private.pem" +CONTAINER_PRIVKEY_ID="0" + +# default ports for zts server. http support is disabled +# https support enabled - must provide certificate for server +CONTAINER_TLS_PORT="8443" +CONTAINER_PORT="0" + +# setup the keystore in pkcs12 format that includes our +# server's private key and x509 cert +CONTAINER_SSL_KEYSTORE="${ROOT}/var/zts_server/certs/zts_keystore.pkcs12" +CONTAINER_SSL_KEYSTORE_TYPE="PKCS12" +CONTAINER_SSL_KEYSTORE_PASSWORD="athenz" + +CONTAINER_SSL_TRUSTSTORE="${ROOT}/var/zts_server/certs/zts_truststore.jks" +CONTAINER_SSL_TRUSTSTORE_TYPE="JKS" +CONTAINER_SSL_TRUSTSTORE_PASSWORD="athenz" + +# ** athenz configuration file +CONTAINER_ATHENZ_CONF="conf/zts_server/athenz.conf" + +# ** logback configuration file +CONTAINER_LOG_CONFIG="${ROOT}/conf/zts_server/logback.xml" + +# ** server settings + +# CONTAINER_LISTEN_HOST= +# CONTAINER_HOSTNAME= +# CONTAINER_ROLE_TOKEN_MAX_TIMEOUT= +# CONTAINER_ROLE_TOKEN_DEFAULT_TIMEOUT= +# CONTAINER_SIGNED_POLICY_TIMEOUT= +# CONTAINER_ZMS_DOMAIN_UPDATE_TIMEOUT= +# CONTAINER_ZMS_DOMAIN_DELETE_TIMEOUT= +# CONTAINER_ZMS_CLIENT_READ_TIMEOUT= + +# ** list of authorized proxy users +# CONTAINER_AUTHZ_PROXY_USERS= + +# ** more ssl settings +# CONTAINER_SSL_KEYMANAGER_PASSWORD= +# CONTAINER_SSL_EXCLUDED_CIPHER_SUITES= +# CONTAINER_SSL_EXCLUDED_PROTOCOLS= + +# ** access log settings +# CONTAINER_ACCESS_LOG_DIR= +# CONTAINER_ACCESS_LOG_NAME= +# CONTAINER_ACCESS_LOG_OPTIONS= +# CONTAINER_ACCESS_LOG_RETAIN_DAYS= +# CONTAINER_ACCESS_LOG_ROTATION_PERIOD= +# CONTAINER_ACCESS_LOG_ROTATION_UNIT= + +# ** configure what implemenation classes to use +# CONTAINER_DATA_CHANGE_LOG_STORE_CLASS= +# CONTAINER_PRIVATE_KEY_STORE_CLASS= +# CONTAINER_CERT_SIGNER_CLASS= +# CONTAINER_HOST_SIGNER_SERVICE= +# CONTAINER_CERTSIGN_BASE_URI= + +# ** kerberos authority settings +# CONTAINER_KRB_KEYTAB= +# CONTAINER_KRB_PRINCIPAL= +# CONTAINER_KRB_TKT_CACHE= +# CONTAINER_KRB_TKT_CACHE_PATH= +# CONTAINER_KRB_TGT_RENEW= +# CONTAINER_KRB_DEBUG= +# CONTAINER_KRB_JAAS_CONF= diff --git a/servers/zts/conf/logback.xml b/servers/zts/conf/logback.xml new file mode 100755 index 00000000000..3d6967870c1 --- /dev/null +++ b/servers/zts/conf/logback.xml @@ -0,0 +1,49 @@ + + + + + + + + + ${LOG_DIR}/server.log + true + + + ${LOG_DIR}/server.%d.log + 7 + true + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n + + + + + + + ${LOG_DIR}/audit.log + true + + + ${LOG_DIR}/audit.%d.log + 30 + true + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n + + + + + + + + + + + + + diff --git a/servers/zts/pom.xml b/servers/zts/pom.xml new file mode 100644 index 00000000000..c3350b9175e --- /dev/null +++ b/servers/zts/pom.xml @@ -0,0 +1,188 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../pom.xml + + + zts_server + zts_server + Athenz ZTS Server + + + + + com.amazonaws + aws-java-sdk-bom + 1.10.67 + pom + import + + + + + + + ${project.groupId} + auth_core + ${project.parent.version} + + + ${project.groupId} + server_common + ${project.parent.version} + + + ${project.groupId} + zts_core + ${project.parent.version} + + + ${project.groupId} + zms_java_client + ${project.parent.version} + + + ${project.groupId} + client_common + ${project.parent.version} + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey.version} + + + com.fasterxml.jackson.core + jackson-annotations + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + commons-daemon + commons-daemon + 1.0.15 + + + com.google.guava + guava + 16.0.1 + + + org.bouncycastle + bcprov-jdk15on + 1.53 + + + org.bouncycastle + bcpkix-jdk15on + 1.53 + + + org.glassfish.jersey.containers + jersey-container-servlet + ${jersey.version} + + + org.eclipse.jetty + jetty-servlet + ${jetty.version} + + + org.eclipse.jetty + jetty-util + ${jetty.version} + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + org.eclipse.jetty + jetty-security + ${jetty.version} + + + org.eclipse.jetty + jetty-rewrite + ${jetty.version} + + + com.amazonaws + aws-java-sdk-s3 + + + com.amazonaws + aws-java-sdk-sts + + + ch.qos.logback + logback-classic + 1.1.3 + + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.1.1 + + + + exec + + generate-sources + + + + bash + + scripts/make_stubs.sh + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.0 + + + copy-dependencies + package + + copy-dependencies + + + runtime + + + + + + + diff --git a/servers/zts/scripts/make_stubs.sh b/servers/zts/scripts/make_stubs.sh new file mode 100755 index 00000000000..d667b28f744 --- /dev/null +++ b/servers/zts/scripts/make_stubs.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# If the zts_core dependency has been updated, then this script should be run +# manually to pick up the latest rdl to generate the appropriate server stubs. + +# Note this script is dependent on the rdl utility. +# +# Use open source version of rdl https://github.com/ardielle/ardielle-tools +# go get github.com/ardielle/ardielle-tools/... + +command -v rdl >/dev/null 2>&1 || { + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "SOURCE WARNING"; + echo >&2 "------------------------------------------------------------------------"; + echo >&2 "Please install rdl utility: go get github.com/ardielle/ardielle-tools/..."; + echo >&2 "Skipping source generation..."; + exit 0; +} + +echo "Update the ZTS.rdl to define string type" +RDL_FILE=src/main/rdl/ZTS.rdl + +echo "Generate the ZTS server stubs" +rdl -s generate -o src/main/java java-server $RDL_FILE + +echo "Removing not needed ZTS Server file..." +rm src/main/java/com/yahoo/athenz/zts/ZTSServer.java diff --git a/servers/zts/scripts/zts_debug.sh b/servers/zts/scripts/zts_debug.sh new file mode 100755 index 00000000000..5a991653cbc --- /dev/null +++ b/servers/zts/scripts/zts_debug.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# setup ZTS settings for debug run + +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.home=./" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.zms_url=http://localhost:4080/" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.authority_classes=com.yahoo.athenz.common.server.debug.DebugPrincipalAuthority,com.yahoo.athenz.common.server.debug.DebugUserAuthority,com.yahoo.athenz.common.server.debug.DebugRoleAuthority,com.yahoo.athenz.common.server.debug.DebugKerberosAuthority" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.port=8080" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.privatekey=src/test/resources/zts_private.pem" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.private_key_store_class=com.yahoo.athenz.zts.pkey.file.FilePrivateKeyStoreFactory" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.privatekey_id=0" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.access_log_dir=./zts_logs" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.enable_stats=false" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.zms_domain_update_timeout=30" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.zms_domain_delete_timeout=60" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.cert_signer_class=com.yahoo.athenz.zts.cert.MockCertSignerFactory" +export ZTS_OPTS="${ZTS_OPTS} -Dlogback.configurationFile=src/test/resources/logback.xml" +export ZTS_OPTS="${ZTS_OPTS} -Dathenz.athenz_conf=src/test/resources/athenz.conf" + +if [ "$1" == "-ssl" ]; then + export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.tls_port=8443" + export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.ssl_key_store_password=password" + export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.ssl_key_store=file://$HOME/.keystore" + export ZTS_OPTS="${ZTS_OPTS} -Dathenz.zts.ssl_key_store_type=PKCS12" +fi + +# make sure the access log directory exists + +mkdir -p ./zts_logs + +mvn exec:java -Dexec.mainClass="com.yahoo.athenz.zts.ZTS" ${ZTS_OPTS} -Dexec.classpathScope="test" diff --git a/servers/zts/scripts/zts_start.sh b/servers/zts/scripts/zts_start.sh new file mode 100644 index 00000000000..ea793ce0794 --- /dev/null +++ b/servers/zts/scripts/zts_start.sh @@ -0,0 +1,190 @@ +#!/bin/bash + +if [ -z "${ROOT}" ]; then + ROOT="/home/athenz" +fi + +CONTAINER_NAME=zts_server + +## pick up our service settings which should override +# some of the default values set by the code +if [ ! -f ${ROOT}/conf/${CONTAINER_NAME}/container_settings ]; then + echo "Unable to find container settings: ${ROOT}/conf/${CONTAINER_NAME}/container_settings aborting" + exit -1 +fi + +. "${ROOT}/conf/${CONTAINER_NAME}/container_settings" + +CONTAINER_RUN_PATH=${ROOT}/var/run/zts_server +CONTAINER_CLASSPATH=${ROOT}/lib/jar/zts_server*.jar:${ROOT}/lib/jars/* +CONTAINER_BOOTSTRAP_CLASS=com.yahoo.athenz.zts.ZTS + +export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.port=${CONTAINER_PORT}" +export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.tls_port=${CONTAINER_TLS_PORT}" +export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.logs=${ROOT}/logs/${CONTAINER_NAME}" + +if [ "x${CONTAINER_LOG_CONFIG}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dlogback.configurationFile=${CONTAINER_LOG_CONFIG}" +fi + +if [ "x${CONTAINER_PRIVKEY}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.privatekey=${CONTAINER_PRIVKEY}" +fi + +if [ "x${CONTAINER_PRIVKEY_ID}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.privatekey_id=${CONTAINER_PRIVKEY_ID}" +fi + +if [ "x${CONTAINER_HOSTNAME}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.hostname=${CONTAINER_HOSTNAME}" +fi + +if [ "x${CONTAINER_ROLE_TOKEN_MAX_TIMEOUT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.role_token_max_timeout=${CONTAINER_ROLE_TOKEN_MAX_TIMEOUT}" +fi + +if [ "x${CONTAINER_ROLE_TOKEN_DEFAULT_TIMEOUT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.role_token_default_timeout=${CONTAINER_ROLE_TOKEN_DEFAULT_TIMEOUT}" +fi + +if [ "x${CONTAINER_SIGNED_POLICY_TIMEOUT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.signed_policy_timeout=${CONTAINER_SIGNED_POLICY_TIMEOUT}" +fi + +if [ "x${CONTAINER_ZMS_DOMAIN_UPDATE_TIMEOUT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.zms_domain_update_timeout=${CONTAINER_ZMS_DOMAIN_UPDATE_TIMEOUT}" +fi + +if [ "x${CONTAINER_ZMS_DOMAIN_DELETE_TIMEOUT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.zms_domain_delete_timeout=${CONTAINER_ZMS_DOMAIN_DELETE_TIMEOUT}" +fi + +if [ "x${CONTAINER_ZMS_CLIENT_READ_TIMEOUT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dyahoo.zms_java_client.read_timeout=${CONTAINER_ZMS_CLIENT_READ_TIMEOUT}" +fi + +if [ "x${CONTAINER_SSL_KEYSTORE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.ssl_key_store=${CONTAINER_SSL_KEYSTORE}" +fi + +if [ "x${CONTAINER_SSL_KEYSTORE_TYPE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.ssl_key_store_type=${CONTAINER_SSL_KEYSTORE_TYPE}" +fi + +if [ "x${CONTAINER_SSL_KEYSTORE_PASSWORD}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.ssl_key_store_password=${CONTAINER_SSL_KEYSTORE_PASSWORD}" +fi + +if [ "x${CONTAINER_SSL_TRUSTSTORE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.ssl_trust_store=${CONTAINER_SSL_TRUSTSTORE}" + export JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStore=${CONTAINER_SSL_TRUSTSTORE}" +fi + +if [ "x${CONTAINER_SSL_TRUSTSTORE_TYPE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.ssl_trust_store_type=${CONTAINER_SSL_TRUSTSTORE_TYPE}" +fi + +if [ "x${CONTAINER_SSL_TRUSTSTORE_PASSWORD}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.ssl_trust_store_password=${CONTAINER_SSL_TRUSTSTORE_PASSWORD}" +fi + +if [ "x${CONTAINER_SSL_KEYMANAGER_PASSWORD}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.ssl_key_manager_password=${CONTAINER_SSL_KEYMANAGER_PASSWORD}" +fi + +if [ "x${CONTAINER_SSL_EXCLUDED_CIPHER_SUITES}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.ssl_excluded_cipher_suites=${CONTAINER_SSL_EXCLUDED_CIPHER_SUITES}" +fi + +if [ "x${CONTAINER_SSL_EXCLUDED_PROTOCOLS}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.ssl_excluded_protocols=${CONTAINER_SSL_EXCLUDED_PROTOCOLS}" +fi + +if [ "x${CONTAINER_ACCESS_LOG_DIR}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.access_log_dir=${CONTAINER_ACCESS_LOG_DIR}" +fi + +if [ "x${CONTAINER_ACCESS_LOG_NAME}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.access_log_name=${CONTAINER_ACCESS_LOG_NAME}" +fi + +if [ "x${CONTAINER_ACCESS_LOG_OPTIONS}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.access_log_options=${CONTAINER_ACCESS_LOG_OPTIONS}" +fi + +if [ "x${CONTAINER_ACCESS_LOG_RETAIN_DAYS}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.access_log_retain_days=${CONTAINER_ACCESS_LOG_RETAIN_DAYS}" +fi + +if [ "x${CONTAINER_ACCESS_LOG_ROTATION_PERIOD}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.access_log_rotation_period=${CONTAINER_ACCESS_LOG_ROTATION_PERIOD}" +fi + +if [ "x${CONTAINER_ACCESS_LOG_ROTATION_UNIT}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.access_log_rotation_unit=${CONTAINER_ACCESS_LOG_ROTATION_UNIT}" +fi + +if [ "x${CONTAINER_LISTEN_HOST}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.listen_host=${CONTAINER_LISTEN_HOST}" +fi + +if [ "x${CONTAINER_DATA_CHANGE_LOG_STORE_CLASS}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.data_change_log_store_class=${CONTAINER_DATA_CHANGE_LOG_STORE_CLASS}" +fi + +if [ "x${CONTAINER_PRIVATE_KEY_STORE_CLASS}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.private_key_store_class=${CONTAINER_PRIVATE_KEY_STORE_CLASS}" +fi + +if [ "x${CONTAINER_CERT_SIGNER_CLASS}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.cert_signer_class=${CONTAINER_CERT_SIGNER_CLASS}" +fi + +if [ "x${CONTAINER_CERTSIGN_BASE_URI}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.certsign_base_uri=${CONTAINER_CERTSIGN_BASE_URI}" +fi + +if [ "x${CONTAINER_HOST_SIGNER_SERVICE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.host_signer_service=${CONTAINER_HOST_SIGNER_SERVICE}" +fi + + +if [ "x${CONTAINER_KRB_KEYTAB}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dyahoo.auth_core.keytab_location=${CONTAINER_KRB_KEYTAB}" +fi + +if [ "x${CONTAINER_KRB_PRINCIPAL}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dyahoo.auth_core.service_principal=${CONTAINER_KRB_PRINCIPAL}" +fi + +if [ "x${CONTAINER_KRB_TKT_CACHE}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dyahoo.auth_core.use_ticket_cache=${CONTAINER_KRB_TKT_CACHE}" +fi + +if [ "x${CONTAINER_KRB_TKT_CACHE_PATH}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dyahoo.auth_core.ticket_cache_name=${CONTAINER_KRB_TKT_CACHE_PATH}" +fi + +if [ "x${CONTAINER_KRB_TGT_RENEW}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dyahoo.auth_core.renewTGT=${CONTAINER_KRB_TGT_RENEW}" +fi + +if [ "x${CONTAINER_KRB_DEBUG}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dsun.security.krb5.debug=${CONTAINER_KRB_DEBUG}" +fi + +if [ "x${CONTAINER_KRB_JAAS_CONF}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Djava.security.auth.login.config=${CONTAINER_KRB_JAAS_CONF}" +fi + +if [ "x${CONTAINER_AUTHZ_PROXY_USERS}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zts.authorized_proxy_users=${CONTAINER_AUTHZ_PROXY_USERS}" +fi + +if [ "x${CONTAINER_ATHENZ_CONF}" != "x" ]; then + export JAVA_OPTS="${JAVA_OPTS} -Dathenz.athenz_conf=${CONTAINER_ATHENZ_CONF}" +fi + +echo "Executing: java -classpath ${CONTAINER_CLASSPATH} ${JAVA_OPTS} ${CONTAINER_BOOTSTRAP_CLASS}" + +java -classpath ${CONTAINER_CLASSPATH} ${JAVA_OPTS} ${CONTAINER_BOOTSTRAP_CLASS} 2>&1 < /dev/null diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/GetDomainSignedPolicyDataResult.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/GetDomainSignedPolicyDataResult.java new file mode 100644 index 00000000000..fc42b4d7fdb --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/GetDomainSignedPolicyDataResult.java @@ -0,0 +1,49 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zts; + +import com.yahoo.rdl.*; +import java.util.*; +import javax.ws.rs.core.Response; +import javax.ws.rs.WebApplicationException; + +public final class GetDomainSignedPolicyDataResult { + private ResourceContext context; + private String domainName; + private int code; //normal result + + GetDomainSignedPolicyDataResult(ResourceContext context) { + this.context = context; + this.code = 0; + } + + public boolean isAsync() { + return false; + } + + public void done(int code, DomainSignedPolicyData domainSignedPolicyData, String tag) { + Response resp = Response.status(code).entity(domainSignedPolicyData) + .header("ETag", tag) + .build(); + throw new WebApplicationException(resp); + } + + public void done(int code) { + done(code, new ResourceError().code(code).message(ResourceException.codeToString(code)), ""); + } + + public void done(int code, String tag) { + done(code, new ResourceError().code(code).message(ResourceException.codeToString(code)), tag); + } + + public void done(int code, Object entity, String tag) { + this.code = code; + //to do: check if the exception is declared, and that the entity is of the declared type + WebApplicationException err = new WebApplicationException(Response.status(code).entity(entity) + .header("ETag", tag) + .build()); + throw err; //not optimal + } + +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceContext.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceContext.java new file mode 100644 index 00000000000..cd4984e3097 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceContext.java @@ -0,0 +1,17 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zts; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +// +// ResourceContext +// +public interface ResourceContext { + public HttpServletRequest request(); + public HttpServletResponse response(); + public void authenticate(); + public void authorize(String action, String resource, String trustedDomain); +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceError.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceError.java new file mode 100644 index 00000000000..ddd96536251 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceError.java @@ -0,0 +1,24 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zts; + +public class ResourceError { + + public int code; + public String message; + + public ResourceError code(int code) { + this.code = code; + return this; + } + public ResourceError message(String message) { + this.message = message; + return this; + } + + public String toString() { + return "{code: " + code + ", message: \"" + message + "\"}"; + } + +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceException.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceException.java new file mode 100644 index 00000000000..4d094b4ee5c --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ResourceException.java @@ -0,0 +1,79 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zts; + +public class ResourceException extends RuntimeException { + public final static int OK = 200; + public final static int CREATED = 201; + public final static int ACCEPTED = 202; + public final static int NO_CONTENT = 204; + public final static int MOVED_PERMANENTLY = 301; + public final static int FOUND = 302; + public final static int SEE_OTHER = 303; + public final static int NOT_MODIFIED = 304; + public final static int TEMPORARY_REDIRECT = 307; + public final static int BAD_REQUEST = 400; + public final static int UNAUTHORIZED = 401; + public final static int FORBIDDEN = 403; + public final static int NOT_FOUND = 404; + public final static int CONFLICT = 409; + public final static int GONE = 410; + public final static int PRECONDITION_FAILED = 412; + public final static int UNSUPPORTED_MEDIA_TYPE = 415; + public final static int INTERNAL_SERVER_ERROR = 500; + public final static int NOT_IMPLEMENTED = 501; + + public final static int SERVICE_UNAVAILABLE = 503; + + public static String codeToString(int code) { + switch (code) { + case OK: return "OK"; + case CREATED: return "Created"; + case ACCEPTED: return "Accepted"; + case NO_CONTENT: return "No Content"; + case MOVED_PERMANENTLY: return "Moved Permanently"; + case FOUND: return "Found"; + case SEE_OTHER: return "See Other"; + case NOT_MODIFIED: return "Not Modified"; + case TEMPORARY_REDIRECT: return "Temporary Redirect"; + case BAD_REQUEST: return "Bad Request"; + case UNAUTHORIZED: return "Unauthorized"; + case FORBIDDEN: return "Forbidden"; + case NOT_FOUND: return "Not Found"; + case CONFLICT: return "Conflict"; + case GONE: return "Gone"; + case PRECONDITION_FAILED: return "Precondition Failed"; + case UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type"; + case INTERNAL_SERVER_ERROR: return "Internal Server Error"; + case NOT_IMPLEMENTED: return "Not Implemented"; + default: return "" + code; + } + } + + int code; + Object data; + + public ResourceException(int code) { + this(code, new ResourceError().code(code).message(codeToString(code))); + } + + public ResourceException(int code, Object data) { + super("ResourceException (" + code + "): " + data); + this.code = code; + this.data = data; + } + + public int getCode() { + return code; + } + + public Object getData() { + return data; + } + + public T getData(Class cl) { + return cl.cast(data); + } + +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/RsrcCtxWrapper.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/RsrcCtxWrapper.java new file mode 100644 index 00000000000..92fa8ffcc6f --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/RsrcCtxWrapper.java @@ -0,0 +1,111 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import com.yahoo.athenz.auth.Authorizer; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.KerberosAuthority; +import com.yahoo.athenz.common.server.rest.Http; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class RsrcCtxWrapper implements ResourceContext { + + static final String HEADER_NAME_KRB_AUTH = "Authorization"; + static final String HEADER_NAME_WWW_AUTHENTICATE = "WWW-Authenticate"; + static final String HEADER_VALUE_NEGOTIATE = "negotiate"; + + com.yahoo.athenz.common.server.rest.ResourceContext ctx = null; + + public RsrcCtxWrapper(HttpServletRequest request, HttpServletResponse response, Http.AuthorityList authList, Authorizer authorizer) { + ctx = new com.yahoo.athenz.common.server.rest.ResourceContext(request, response, authList, authorizer); + } + + public com.yahoo.athenz.common.server.rest.ResourceContext context() { + return ctx; + } + + public Principal principal() { + return ctx.principal(); + } + + @Override + public HttpServletRequest request() { + return ctx.request(); + } + + @Override + public HttpServletResponse response() { + return ctx.response(); + } + + @Override + public void authenticate() { + try { + ctx.authenticate(); + } catch (com.yahoo.athenz.common.server.rest.ResourceException restExc) { + throwZtsException(restExc); + } + } + + public void authenticateKerberos() { + try { + ctx.authenticate(); + + // we must verify that the authority is the kerberos + // authority responsible for authentication + + if (!(ctx.principal().getAuthority() instanceof KerberosAuthority)) { + throw new com.yahoo.athenz.common.server.rest.ResourceException(com.yahoo.athenz.common.server.rest.ResourceException.UNAUTHORIZED); + } + + } catch (com.yahoo.athenz.common.server.rest.ResourceException restExc) { + + // if the request does not contain kerberos authorization header, + // we're going to add the expected header to the response + + if (ctx.request().getHeader(HEADER_NAME_KRB_AUTH) == null) { + ctx.response().addHeader(HEADER_NAME_WWW_AUTHENTICATE, HEADER_VALUE_NEGOTIATE); + } + + throwZtsException(restExc); + } + } + + @Override + public void authorize(String action, String resource, String trustedDomain) { + try { + ctx.authorize(action, resource, trustedDomain); + } catch (com.yahoo.athenz.common.server.rest.ResourceException restExc) { + throwZtsException(restExc); + } + } + + public void throwZtsException(com.yahoo.athenz.common.server.rest.ResourceException restExc) { + String msg = null; + Object data = restExc.getData(); + if (data instanceof String) { + msg = (String) data; + } + if (msg == null) { + msg = restExc.getMessage(); + } + throw new com.yahoo.athenz.zts.ResourceException(restExc.getCode(), + new ResourceError().code(restExc.getCode()).message(msg)); + } + +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTS.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTS.java new file mode 100644 index 00000000000..73e510b380e --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTS.java @@ -0,0 +1,393 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import java.net.InetAddress; +import java.security.PrivateKey; + +import org.eclipse.jetty.server.HttpConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.AuthorityKeyStore; +import com.yahoo.athenz.auth.Authorizer; +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.metrics.MetricFactory; +import com.yahoo.athenz.common.server.log.AuditLogFactory; +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.common.server.rest.Http.AuthorityList; +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.cert.CertSignerFactory; +import com.yahoo.athenz.zts.cert.SvcCertStore; +import com.yahoo.athenz.zts.cert.SvcCertStoreFactory; +import com.yahoo.athenz.zts.pkey.PrivateKeyStore; +import com.yahoo.athenz.zts.pkey.PrivateKeyStoreFactory; +import com.yahoo.athenz.zts.store.ChangeLogStore; +import com.yahoo.athenz.zts.store.ChangeLogStoreFactory; +import com.yahoo.athenz.zts.store.CloudStore; +import com.yahoo.athenz.zts.store.DataStore; + +public class ZTS { + + private static final Logger LOG = LoggerFactory.getLogger(ZTS.class); + + static final String ZTS_PRINCIPAL_AUTHORITY_CLASS = "com.yahoo.athenz.auth.impl.PrincipalAuthority"; + static final String ZTS_CHANGE_LOG_STORE_CLASS = "com.yahoo.athenz.zts.store.file.ZMSFileChangeLogStoreFactory"; + static final String ZTS_PKEY_STORE_CLASS = "com.yahoo.athenz.zts.pkey.file.FilePrivateKeyStoreFactory"; + static final String ZTS_CERT_SIGNER_CLASS = "com.yahoo.athenz.zts.cert.impl.YCertSignerFactory"; + static final String ZTS_SVC_CERT_STORE_CLASS = "com.yahoo.athenz.zts.cert.impl.YSvcCertStoreFactory"; + + // This String is used to create the desired AuditLogMsgBuilder object. + // Its OK if its null, we will just get the default msg builder. + // + private static String AUDIT_LOG_MSG_BLDR_CLASS; + + static { + try { + AUDIT_LOG_MSG_BLDR_CLASS = System.getProperty(ZTSConsts.ZTS_PROP_AUDIT_LOG_MSG_BLDR_CLASS); + // test the class to ensure it is valid + try { + @SuppressWarnings("unused") + AuditLogMsgBuilder msgBldr = AuditLogFactory.getMsgBuilder(AUDIT_LOG_MSG_BLDR_CLASS); + } catch (Exception exc) { + LOG.warn("AuditLogMsgBuilder: Cannot instantiate message builder class from=" + + AUDIT_LOG_MSG_BLDR_CLASS + ", therefore will use default log message builder class instead.", exc); + AUDIT_LOG_MSG_BLDR_CLASS = null; + } + } catch (Exception exc) { + LOG.warn("Failed to get the audit log message builder class using property=" + + ZTSConsts.ZTS_PROP_AUDIT_LOG_MSG_BLDR_CLASS + + ", ZTS will use the default log message builder class instead.", exc); + AUDIT_LOG_MSG_BLDR_CLASS = null; + } + } + + private static final AuditLogger AUDITLOG = getAuditLogger(); + + // Creates an AuditLogger + // + static AuditLogger getAuditLogger() { + String auditLoggerClassName = System.getProperty(ZTSConsts.ZTS_PROP_AUDIT_LOGGER_CLASS); + String auditLoggerClassNameParam = System.getProperty(ZTSConsts.ZTS_PROP_AUDIT_LOGGER_CLASS_PARAM); + AuditLogger auditLog = null; + try { + if (auditLoggerClassNameParam != null) { + auditLog = AuditLogFactory.getLogger(auditLoggerClassName, auditLoggerClassNameParam); + } else { + auditLog = AuditLogFactory.getLogger(auditLoggerClassName); + } + } catch (Exception exc) { + LOG.warn("Failed to create audit logger from class=" + + auditLoggerClassName + ", ZTS will use the default logger instead.", exc); + auditLog = AuditLogFactory.getLogger(); + } + return auditLog; + } + + static Authority getAuthority(String className) { + + LOG.debug("Loading authority {}...", className); + + Authority authority = null; + try { + authority = (Authority) Class.forName(className).newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + LOG.error("Invalid Authority class: " + className + " error: " + e.getMessage()); + return null; + } + return authority; + } + + static String getServerHostName() { + + String serverHostName = System.getProperty(ZTSConsts.ZTS_PROP_HOSTNAME); + if (serverHostName == null || serverHostName.isEmpty()) { + try { + InetAddress localhost = java.net.InetAddress.getLocalHost(); + serverHostName = localhost.getCanonicalHostName(); + } catch (java.net.UnknownHostException e) { + LOG.info("Unable to determine local hostname: " + e.getMessage()); + serverHostName = "localhost"; + } + } + + return serverHostName; + } + + static int getPortNumber(String property, int defaultValue) { + + String propValue = System.getProperty(property); + if (propValue == null) { + return defaultValue; + } + + int port = defaultValue; + try { + + // first try to convert the string property to integer + + port = Integer.parseInt(propValue); + + // now verify that it's a valid port number + + if (port < 0 || port > 65535) { + throw new NumberFormatException(); + } + + } catch (NumberFormatException ex) { + LOG.info("Invalid port: " + propValue + ". Using default port: " + defaultValue); + port = defaultValue; + } + + return port; + } + + static CertSigner getCertSigner() { + + String certSignerFactoryClass = System.getProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_CLASS, ZTS_CERT_SIGNER_CLASS); + CertSignerFactory certSignerFactory = null; + try { + certSignerFactory = (CertSignerFactory) Class.forName(certSignerFactoryClass).newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + LOG.error("Invalid CertSigerFactory class: " + certSignerFactoryClass + + " error: " + e.getMessage()); + return null; + } + + // create our cert signer instance + + return certSignerFactory.create(null); + } + + static SvcCertStore getSvcCertStore(CertSigner certSigner) { + + String svcCertStoreFactoryClass = System.getProperty(ZTSConsts.ZTS_PROP_SVC_CERT_STORE_CLASS, ZTS_SVC_CERT_STORE_CLASS); + SvcCertStoreFactory svcCertStoreFactory = null; + try { + svcCertStoreFactory = (SvcCertStoreFactory) Class.forName(svcCertStoreFactoryClass).newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + LOG.error("Invalid CertSigerFactory class: " + svcCertStoreFactoryClass + + " error: " + e.getMessage()); + return null; + } + + // create our svc cert store instance + + return svcCertStoreFactory.create(certSigner); + } + + static Metric getMetric() { + + String metricFactoryClass = System.getProperty(ZTSConsts.ZTS_PROP_METRIC_FACTORY_CLASS, ZTSConsts.ZTS_METRIC_FACTORY_CLASS); + boolean statsEnabled = Boolean.parseBoolean(System.getProperty(ZTSConsts.ZTS_PROP_STATS_ENABLED, "false")); + if (!statsEnabled && !metricFactoryClass.equals(ZTSConsts.ZTS_METRIC_FACTORY_CLASS)) { + LOG.warn("Override users metric factory property with default since stats are disabled"); + metricFactoryClass = ZTSConsts.ZTS_METRIC_FACTORY_CLASS; + } + + MetricFactory metricFactory = null; + try { + metricFactory = (MetricFactory) Class.forName(metricFactoryClass).newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + LOG.error("Invalid MetricFactory class: " + metricFactoryClass + + " error: " + e.getMessage()); + return null; + } + + Metric metric = metricFactory.create(); + if (metric != null) { + metric.increment("zts_startup"); + } + return metric; + } + + static PrivateKeyStore getPrivateKeyStore(String serverHostName) { + + String pkeyFactoryClass = System.getProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY_STORE_CLASS, ZTS_PKEY_STORE_CLASS); + PrivateKeyStoreFactory pkeyFactory = null; + try { + pkeyFactory = (PrivateKeyStoreFactory) Class.forName(pkeyFactoryClass).newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + LOG.error("Invalid PrivateKeyStoreFactory class: " + pkeyFactoryClass + + " error: " + e.getMessage()); + return null; + } + + return pkeyFactory.create(serverHostName); + } + + + private static ChangeLogStore getChangeLogStore(String homeDir, PrivateKey pkey, String pkeyId, + CloudStore cloudStore) { + + String clogFactoryClass = System.getProperty(ZTSConsts.ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS, ZTS_CHANGE_LOG_STORE_CLASS); + ChangeLogStoreFactory clogFactory = null; + try { + clogFactory = (ChangeLogStoreFactory) Class.forName(clogFactoryClass).newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + LOG.error("Invalid ChangeLogStoreFactory class: " + clogFactoryClass + + " error: " + e.getMessage()); + return null; + } + + // create our struct store + + return clogFactory.create(homeDir, pkey, pkeyId, cloudStore); + } + + public static ZTSJettyContainer createJettyContainer() { + + String root = System.getenv("ROOT"); + if (root == null) { + root = "/home/athenz"; + } + + String homeDir = System.getProperty(ZTSConsts.ZTS_PROP_HOME, root + "/var/zts_server"); + + // retrieve our http and https port numbers + + int httpPort = getPortNumber(ZTSConsts.ZTS_PROP_HTTP_PORT, ZTSConsts.ZTS_HTTP_PORT_DEFAULT); + int httpsPort = getPortNumber(ZTSConsts.ZTS_PROP_HTTPS_PORT, ZTSConsts.ZTS_HTTPS_PORT_DEFAULT); + + String serverHostName = getServerHostName(); + + // get our authorities + + String authListConfig = System.getProperty(ZTSConsts.ZTS_PROP_AUTHORITY_CLASSES, ZTS_PRINCIPAL_AUTHORITY_CLASS); + AuthorityList authorities = new AuthorityList(); + + String[] authorityList = authListConfig.split(","); + for (int idx = 0; idx < authorityList.length; idx++) { + Authority authority = getAuthority(authorityList[idx]); + if (authority == null) { + return null; + } + authority.initialize(); + authorities.add(authority); + } + + PrivateKeyStore keyStore = getPrivateKeyStore(serverHostName); + if (keyStore == null) { + return null; + } + + CertSigner certSigner = getCertSigner(); + if (certSigner == null) { + return null; + } + + Metric metric = getMetric(); + if (metric == null) { + return null; + } + + /// extract our official per-host ZTS private key + + StringBuilder privKeyId = new StringBuilder(256); + PrivateKey pkey = keyStore.getHostPrivateKey(privKeyId); + + // create our cloud store if configured + + CloudStore cloudStore = new CloudStore(certSigner); + + // create our SvcCertStore + + SvcCertStore svcCertStore = getSvcCertStore(certSigner); + if (svcCertStore == null) { + return null; + } + + // create our change log store + + ChangeLogStore clogStore = getChangeLogStore(homeDir, pkey, privKeyId.toString(), cloudStore); + if (clogStore == null) { + return null; + } + + // create our data store + + DataStore dataStore = new DataStore(clogStore, cloudStore); + + // Initialize our storage subsystem which would load all data into + // memory and if necessary retrieve the data from ZMS. It will also + // create the thread to monitor for changes from ZMS + + if (!dataStore.init()) { + metric.increment("zts_startup_fail_sum"); + throw new ResourceException(500, "Unable to initialize storage subsystem"); + } + + // create our Jetty container + + ZTSJettyContainer container = new ZTSJettyContainer(AUDITLOG, AUDIT_LOG_MSG_BLDR_CLASS); + container.resource(ZTSResources.class); + + // Create our ZTS impl handler + + ZTSImpl ztsImpl = null; + try { + ztsImpl = new ZTSImpl(serverHostName, dataStore, cloudStore, svcCertStore, metric, + pkey, privKeyId.toString(), AUDITLOG, AUDIT_LOG_MSG_BLDR_CLASS); + ztsImpl.putAuthorityList(authorities); + } catch (Exception ex) { + metric.increment("zts_startup_fail_sum"); + throw ex; + } + container.delegate(new ZTSBinder(ztsImpl)); + + // make sure to set the keystore for any instance that requires it + + for (Authority authority : authorities.getAuthorities()) { + if (AuthorityKeyStore.class.isInstance(authority)) { + ((AuthorityKeyStore) authority).setKeyStore(ztsImpl); + } + } + + Authorizer authorizer = ztsImpl.getAuthorizer(); + container.authorizer(authorizer); + + container.setBanner("http://" + serverHostName + " http port: " + httpPort + " https port: " + httpsPort); + int maxThreads = Integer.parseInt(System.getProperty(ZTSConsts.ZTS_PROP_MAX_THREADS, "1024")); + container.createServer(maxThreads); + + HttpConfiguration httpConfig = container.newHttpConfiguration(httpsPort); + container.addHTTPConnectors(httpConfig, httpPort, httpsPort); + container.addServletHandlers(homeDir, serverHostName); + + container.addRequestLogHandler(root); + return container; + } + + public static void main(String [] args) { + + System.getProperties().remove("socksProxyHost"); + + try { + ZTSJettyContainer container = createJettyContainer(); + container.run(null); + + } catch (Exception exc) { + + // log that we are shutting down and re-throw the exception + + LOG.error("Startup failure. Shutting down: " + exc.getMessage()); + throw exc; + } + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSAuthorizer.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSAuthorizer.java new file mode 100644 index 00000000000..52bb562381b --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSAuthorizer.java @@ -0,0 +1,379 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Authorizer; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.common.server.util.StringUtils; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zts.cache.DataCache; +import com.yahoo.athenz.zts.store.CloudStore; +import com.yahoo.athenz.zts.store.DataStore; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ZTSAuthorizer implements Authorizer { + + private static final Logger LOGGER = LoggerFactory.getLogger(ZTSAuthorizer.class); + private static final String ASSUME_ROLE = "assume_role"; + final protected DataStore dataStore; + final protected CloudStore cloudStore; + + // enum to represent our access response since in some cases we want to + // handle domain not founds differently instead of just returning failure + + enum AccessStatus { + ALLOWED, + DENIED, + DENIED_DOMAIN_NOT_FOUND, + DENIED_INVALID_ROLE_TOKEN + } + + public ZTSAuthorizer(final DataStore dataStore, final CloudStore cloudStore) { + this.dataStore = dataStore; + this.cloudStore = cloudStore; + } + + @Override + public boolean access(String op, String resource, Principal principal, String trustDomain) { + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + resource = resource.toLowerCase(); + if (trustDomain != null) { + trustDomain = trustDomain.toLowerCase(); + } + op = op.toLowerCase(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("access:(" + op + ", " + resource + ", " + principal + ", " + trustDomain + ")"); + } + + // check to see if the authority is allowed to be processed in + // authorization checks. If this value is false then the principal + // must get a usertoken from ZMS first and the submit the request + // with that token + + if (!authorityAuthorizationAllowed(principal)) { + LOGGER.error("Authority is not allowed to support authorization checks"); + return false; + } + + // retrieve our domain based on resource and action/trustDomain pair + // we want to provider better error reporting to the users so if we get a + // request where the domain is not found instead of just returning 403 + // forbidden (which is confusing since it assumes the user doesn't have + // access as oppose to possible mistype of the domain name by the user) + // we want to return 404 not found. The rest_core has special handling + // for rest.ResourceExceptions so we'll throw that exception in this + // special case of not found domains. + + String domainName = retrieveResourceDomain(resource, op, trustDomain); + if (domainName == null) { + throw new com.yahoo.athenz.zts.ResourceException(ResourceException.NOT_FOUND, "Domain not found"); + } + DataCache domain = dataStore.getDataCache(domainName); + if (domain == null) { + throw new com.yahoo.athenz.zts.ResourceException(ResourceException.NOT_FOUND, "Domain not found"); + } + + AccessStatus accessStatus = evaluateAccess(domain, principal.getYRN(), op, resource, trustDomain); + if (accessStatus == AccessStatus.ALLOWED) { + return true; + } + + return false; + } + + boolean authorityAuthorizationAllowed(Principal principal) { + + Authority authority = principal.getAuthority(); + if (authority == null) { + return true; + } + + return authority.allowAuthorization(); + } + + String retrieveResourceDomain(String resource, String op, String trustDomain) { + + // special handling for ASSUME_ROLE assertions. Since any assertion with + // that action refers to a resource in another domain, there is no point + // to retrieve the domain name from the resource. In these cases the caller + // must specify the trust domain attribute so we'll use that instead and + // if one is not specified then we'll fall back to using the domain name + // from the resource + + String domainName = null; + if (ASSUME_ROLE.equalsIgnoreCase(op) && trustDomain != null) { + domainName = trustDomain; + } else { + domainName = yrnDomain(resource); + } + return domainName; + } + + AccessStatus evaluateAccess(DataCache domain, String identityYRN, String op, String resource, + String trustDomain) { + + AccessStatus accessStatus = AccessStatus.DENIED; + + List policies = domain.getDomainData().getPolicies().getContents().getPolicies(); + List roles = domain.getDomainData().getRoles(); + + for (com.yahoo.athenz.zms.Policy policy : policies) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("evaluateAccess: processing policy: " + policy.getName()); + } + + // we are going to process all the assertions defined in this + // policy. As soon as we get a match for an assertion that + // denies access, we're going to return that result. If we + // get a match for an assertion that allows access we're + // going to remember that result and continue looking at + // all the assertions in case there is something else that + // explicitly denies access + + List assertions = policy.getAssertions(); + if (assertions == null) { + continue; + } + + for (com.yahoo.athenz.zms.Assertion assertion : assertions) { + + // get the effect for the assertion which is set + // as allowed by default + + com.yahoo.athenz.zms.AssertionEffect effect = assertion.getEffect(); + if (effect == null) { + effect = com.yahoo.athenz.zms.AssertionEffect.ALLOW; + } + + // if we have already matched an allow assertion then + // we'll automatically skip any assertion that has + // allow effect since there is no point of matching it + + if (accessStatus == AccessStatus.ALLOWED && effect == com.yahoo.athenz.zms.AssertionEffect.ALLOW) { + continue; + } + + // if no match then process the next assertion + + if (!assertionMatch(assertion, identityYRN, op, resource, roles, trustDomain)) { + continue; + } + + // if the assertion has matched and the effect is deny + // then we're going to return right away otherwise we'll + // set our return allow matched flag to true and continue + // processing other assertions + + if (effect == com.yahoo.athenz.zms.AssertionEffect.DENY) { + return AccessStatus.DENIED; + } + + accessStatus = AccessStatus.ALLOWED; + } + } + + return accessStatus; + } + + boolean assertionMatch(com.yahoo.athenz.zms.Assertion assertion, String identityYRN, String op, String resource, + List roles, String trustDomain) { + + String opPattern = StringUtils.patternFromGlob(assertion.getAction()); + if (!op.matches(opPattern)) { + return false; + } + + String rezPattern = StringUtils.patternFromGlob(assertion.getResource()); + if (!resource.matches(rezPattern)) { + return false; + } + + String rolePattern = StringUtils.patternFromGlob(assertion.getRole()); + boolean matchResult = matchPrincipal(roles, rolePattern, identityYRN, trustDomain); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("assertionMatch: -> " + matchResult + " (effect: " + assertion.getEffect() + ")"); + } + + return matchResult; + } + + boolean matchPrincipal(List roles, String rolePattern, String fullUser, String trustDomain) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("matchPrincipal: rolePattern: " + rolePattern + " user: " + fullUser + + " trust: " + trustDomain); + } + + for (Role role : roles) { + + String name = role.getName(); + if (!name.matches(rolePattern)) { + continue; + } + + if (matchPrincipalInRole(role, name, fullUser, trustDomain)) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("matchPrincipal: assertionMatch: -> OK (by principal)"); + } + return true; + } + } + return false; + } + + boolean matchPrincipalInRole(Role role, String roleName, String fullUser, String trustDomain) { + + // if we have members in the role then we're going to check + // against that list only + + if (role.getMembers() != null) { + return isMemberOfRole(role, fullUser); + } + + // no members so let's check if this is a trust domain + + String trust = role.getTrust(); + if (!shouldRunDelegatedTrustCheck(trust, trustDomain)) { + return false; + } + + // delegate to another domain. + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("matchPrincipalInRole: [delegated trust. Checking with: " + trust + "]"); + } + + return delegatedTrust(trust, roleName, fullUser); + } + + boolean isMemberOfRole(Role role, String member) { + + if (role.getMembers() == null) { + return false; + } + + Set members = new HashSet(role.getMembers()); + return members.contains(member); + } + + + boolean matchDelegatedTrustAssertion(com.yahoo.athenz.zms.Assertion assertion, String roleName, + String roleMember, List roles) { + + if (!ASSUME_ROLE.equalsIgnoreCase(assertion.getAction())) { + return false; + } + + String rezPattern = StringUtils.patternFromGlob(assertion.getResource()); + if (!roleName.matches(rezPattern)) { + return false; + } + + String rolePattern = StringUtils.patternFromGlob(assertion.getRole()); + for (Role role : roles) { + String name = role.getName(); + if (!name.matches(rolePattern)) { + continue; + } + + if (isMemberOfRole(role, roleMember)) { + return true; + } + } + + return false; + } + + boolean matchDelegatedTrustPolicy(com.yahoo.athenz.zms.Policy policy, String roleName, String roleMember, List roles) { + + List assertions = policy.getAssertions(); + if (assertions == null) { + return false; + } + + for (com.yahoo.athenz.zms.Assertion assertion : assertions) { + if (matchDelegatedTrustAssertion(assertion, roleName, roleMember, roles)) { + return true; + } + } + + return false; + } + + boolean delegatedTrust(String domainName, String roleName, String roleMember) { + + DataCache domain = dataStore.getDataCache(domainName); + if (domain == null) { + return false; + } + + for (com.yahoo.athenz.zms.Policy policy : domain.getDomainData().getPolicies().getContents().getPolicies()) { + if (matchDelegatedTrustPolicy(policy, roleName, roleMember, domain.getDomainData().getRoles())) { + return true; + } + } + + return false; + } + + boolean shouldRunDelegatedTrustCheck(String trust, String trustDomain) { + + // if no trust field field then no delegated trust check + + if (trust == null) { + return false; + } + + // if no specific trust domain specifies then we need + // run the delegated trust check for this domain + + if (trustDomain == null) { + return true; + } + + // otherwise we'll run the delegated trust check only if + // domain name matches + + return trust.equalsIgnoreCase(trustDomain); + } + + String yrnDomain(String yrn) { + // supported format: "domain:entity" + String [] s = yrn.split(":"); + if (s.length <= 2) { + return s[0]; + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("yrnDomain: missing domain name: " + yrn); + } + return null; + } + } + +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSBinder.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSBinder.java new file mode 100644 index 00000000000..b6df15b14b5 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSBinder.java @@ -0,0 +1,39 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import org.glassfish.hk2.utilities.binding.AbstractBinder; + +public class ZTSBinder extends AbstractBinder { + private final ZTSImpl ztsImpl; + + public ZTSBinder(final ZTSHandler ztsImpl) { + this.ztsImpl = (ZTSImpl) ztsImpl; + } + + @Override + protected void configure() { + bind(ZTSHandler.class).in(javax.inject.Singleton.class); + bind(ZTSHandler.class.cast(ztsImpl)).to(ZTSHandler.class); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(256); + sb.append("Binder: contains type=").append(ZTSHandler.class).append(" and object=").append(ztsImpl); + return sb.toString(); + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java new file mode 100644 index 00000000000..bdb904ee8fd --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java @@ -0,0 +1,102 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +/** + * Contains constants shared by classes throughout the service. + **/ +public final class ZTSConsts { + // System property names with defaults(where applicable) + + public static final String ZTS_PROP_HTTP_PORT = "athenz.zts.port"; + public static final String ZTS_PROP_HTTPS_PORT = "athenz.zts.tls_port"; + + public static final int ZTS_HTTP_PORT_DEFAULT = 4080; + public static final int ZTS_HTTPS_PORT_DEFAULT = 0; + + public static final String ZTS_PROP_ACCESS_LOG_RETAIN_DAYS = "athenz.zts.access_log_retain_days"; + public static final String ZTS_PROP_SEND_SERVER_VERSION = "athenz.zts.http_send_server_version"; + public static final String ZTS_PROP_SEND_DATE_HEADER = "athenz.zts.http_send_date_header"; + public static final String ZTS_PROP_OUTPUT_BUFFER_SIZE = "athenz.zts.http_output_buffer_size"; + public static final String ZTS_PROP_REQUEST_HEADER_SIZE = "athenz.zts.http_reqeust_header_size"; + public static final String ZTS_PROP_RESPONSE_HEADER_SIZE = "athenz.zts.http_response_header_size"; + public static final String ZTS_PROP_IDLE_TIMEOUT = "athenz.zts.http_idle_timeout"; + public static final String ZTS_PROP_LISTEN_HOST = "athenz.zts.listen_host"; + public static final String ZTS_PROP_KEEP_ALIVE = "athenz.zts.keep_alive"; + public static final String ZTS_PROP_ACCESS_SLF4J_LOGGER = "athenz.zts.access_slf4j_logger"; + public static final String ZTS_PROP_ACCESS_LOG_DIR = "athenz.zts.access_log_dir"; + public static final String ZTS_PROP_ACCESS_LOG_NAME = "athenz.zts.access_log_name"; + public static final String ZTS_PROP_PRIVATE_KEY = "athenz.zts.privatekey"; + public static final String ZTS_PROP_PRIVATE_KEY_ID = "athenz.zts.privatekey_id"; + public static final String ZTS_PROP_PRIVATE_KEY_NAME = "athenz.zts.privatekey_name"; + + public static final String ZTS_PROP_CA_PRIVATE_KEY = "athenz.zts.ca_privatekey"; + public static final String ZTS_PROP_CA_PRIVATE_KEY_PASSWORD = "athenz.zts.ca_privatekey_password"; + public static final String ZTS_PROP_CA_CERTIFICATE = "athenz.zts.ca_certificate"; + + public static final String ZTS_PROP_STATS_ENABLED = "athenz.zts.enable_stats"; + public static final String ZTS_PROP_METRIC_FACTORY_CLASS = "athenz.zts.metric_class"; + + public static final String ZTS_PROP_KEYSTORE_PASSWORD = "athenz.zts.ssl_key_store_password"; + public static final String ZTS_PROP_KEYMANAGER_PASSWORD = "athenz.zts.ssl_key_manager_password"; + public static final String ZTS_PROP_TRUSTSTORE_PASSWORD = "athenz.zts.ssl_trust_store_password"; + public static final String ZTS_PROP_KEYSTORE_PATH = "athenz.zts.ssl_key_store"; + public static final String ZTS_PROP_KEYSTORE_TYPE = "athenz.zts.ssl_key_store_type"; + public static final String ZTS_PROP_TRUSTSTORE_PATH = "athenz.zts.ssl_trust_store"; + public static final String ZTS_PROP_TRUSTSTORE_TYPE = "athenz.zts.ssl_trust_store_type"; + public static final String ZTS_PROP_EXCLUDED_CIPHER_SUITES = "athenz.zts.ssl_excluded_cipher_suites"; + public static final String ZTS_PROP_EXCLUDED_PROTOCOLS = "athenz.zts.ssl_excluded_protocols"; + public static final String ZTS_PROP_DATASTORE_REGEX_CACHE = "athenz.zts.cache.regex.size"; + public static final String ZTS_PROP_WANT_CLIENT_CERT = "athenz.zts.want_client_cert"; + + // properties used to over-ride default Audit logger + // + public static final String ZTS_PROP_AUDIT_LOGGER_CLASS = "athenz.zts.audit_logger_class"; + public static final String ZTS_PROP_AUDIT_LOGGER_CLASS_PARAM = "athenz.zts.audit_logger_class_param"; + public static final String ZTS_PROP_AUDIT_LOG_MSG_BLDR_CLASS = "athenz.zts.audit_log_msg_builder_class"; + + public static final String ZTS_PROP_HOME = "athenz.zts.home"; + public static final String ZTS_PROP_HOSTNAME = "athenz.zts.hostname"; + + public static final String ZTS_PROP_AUTHORITY_CLASSES = "athenz.zts.authority_classes"; + public static final String ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS = "athenz.zts.data_change_log_store_class"; + public static final String ZTS_PROP_PRIVATE_KEY_STORE_CLASS = "athenz.zts.private_key_store_class"; + public static final String ZTS_PROP_CERT_SIGNER_CLASS = "athenz.zts.cert_signer_class"; + public static final String ZTS_PROP_SVC_CERT_STORE_CLASS = "athenz.zts.svc_cert_store_class"; + public static final String ZTS_PROP_MAX_THREADS = "athenz.zts.http_max_threads"; + public static final String ZTS_PROP_LEAST_PRIVILEGE_PRINCIPLE = "athenz.zts.least_privilege_principle"; + + public static final String ZTS_PROP_ROLE_TOKEN_MAX_TIMEOUT = "athenz.zts.role_token_max_timeout"; + public static final String ZTS_PROP_ROLE_TOKEN_DEFAULT_TIMEOUT = "athenz.zts.role_token_default_timeout"; + public static final String ZTS_PROP_SIGNED_POLICY_TIMEOUT = "athenz.zts.signed_policy_timeout"; + public static final String ZTS_PROP_AWS_BOOT_TIME_OFFSET = "athenz.zts.aws_boot_time_offset"; + public static final String ZTS_PROP_SERVICE_TOKEN_TIME_OFFSET = "athenz.zts.service_token_time_offset"; + public static final String ZTS_PROP_AUTHORIZED_PROXY_USERS = "athenz.zts.authorized_proxy_users"; + + public static final String ZTS_PROP_USER_DOMAIN = "athenz.user_domain"; + public static final String ZTS_PROP_ATHENZ_CONF = "athenz.athenz_conf"; + + public static final String ZTS_UNKNOWN_DOMAIN = "unknown_domain"; + public static final String ATHENZ_SYS_DOMAIN = "sys.auth"; + + public static final String STR_DEF_ROOT = "/home/athenz"; + public static final String STR_ENV_ROOT = "ROOT"; + + public static final String ZTS_PROP_AWS_ENABLED = "athenz.zts.aws_enabled"; + + public static final String ZTS_METRIC_FACTORY_CLASS = "com.yahoo.athenz.common.metrics.impl.NoOpMetricFactory"; +} + diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSDaemon.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSDaemon.java new file mode 100644 index 00000000000..0c0f13a57c8 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSDaemon.java @@ -0,0 +1,40 @@ +package com.yahoo.athenz.zts; +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import org.apache.commons.daemon.Daemon; +import org.apache.commons.daemon.DaemonContext; + +public class ZTSDaemon implements Daemon { + + private String[] args = null; + + public void init(DaemonContext context) throws Exception { + args = context.getArguments(); + } + + public void start() throws Exception { + if (args == null) { + return; + } + ZTS.main(args); + } + + public void stop() throws Exception { + } + + public void destroy() { + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSHandler.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSHandler.java new file mode 100644 index 00000000000..96530a98a65 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSHandler.java @@ -0,0 +1,32 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zts; + +import com.yahoo.rdl.*; +import java.util.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +// +// ZTSHandler is the interface that the service implementation must implement +// +public interface ZTSHandler { + public ServiceIdentity getServiceIdentity(ResourceContext context, String domainName, String serviceName); + public ServiceIdentityList getServiceIdentityList(ResourceContext context, String domainName); + public PublicKeyEntry getPublicKeyEntry(ResourceContext context, String domainName, String serviceName, String keyId); + public HostServices getHostServices(ResourceContext context, String host); + public void getDomainSignedPolicyData(ResourceContext context, String domainName, String matchingTag, GetDomainSignedPolicyDataResult result); + public RoleToken getRoleToken(ResourceContext context, String domainName, String role, Integer minExpiryTime, Integer maxExpiryTime, String proxyForPrincipal); + public Access getAccess(ResourceContext context, String domainName, String roleName, String principal); + public RoleAccess getRoleAccess(ResourceContext context, String domainName, String principal); + public TenantDomains getTenantDomains(ResourceContext context, String providerDomainName, String userName, String roleName, String serviceName); + public Identity postInstanceInformation(ResourceContext context, InstanceInformation info); + public Identity postInstanceRefreshRequest(ResourceContext context, String domain, String service, InstanceRefreshRequest req); + public Identity postAWSInstanceInformation(ResourceContext context, AWSInstanceInformation info); + public Identity postAWSCertificateRequest(ResourceContext context, String domain, String service, AWSCertificateRequest req); + public AWSTemporaryCredentials getAWSTemporaryCredentials(ResourceContext context, String domainName, String role); + public DomainMetrics postDomainMetrics(ResourceContext context, String domainName, DomainMetrics req); + public Schema getRdlSchema(ResourceContext context); + public ResourceContext newResourceContext(HttpServletRequest request, HttpServletResponse response); +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java new file mode 100644 index 00000000000..684a2515f54 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java @@ -0,0 +1,1609 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.EntityTag; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Authorizer; +import com.yahoo.athenz.auth.KeyStore; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.CertificateAuthority; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.server.log.AuditLogFactory; +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.common.server.rest.Http; +import com.yahoo.athenz.common.server.util.ServletRequestUtil; +import com.yahoo.athenz.common.utils.SignUtils; +import com.yahoo.athenz.zms.DomainData; +import com.yahoo.athenz.zts.cache.DataCache; +import com.yahoo.athenz.zts.cert.SvcCertStore; +import com.yahoo.athenz.zts.store.CloudStore; +import com.yahoo.athenz.zts.store.DataStore; +import com.yahoo.athenz.zts.utils.ZTSUtils; +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Schema; +import com.yahoo.rdl.Struct; +import com.yahoo.rdl.Timestamp; +import com.yahoo.rdl.Validator; +import com.yahoo.rdl.Validator.Result; + +/** + * An implementation of ZTS. + */ +public class ZTSImpl implements KeyStore, ZTSHandler { + + protected DataStore dataStore; + protected CloudStore cloudStore; + protected SvcCertStore svcCertStore; + protected Metric metric; + protected Schema schema; + protected PrivateKey privateKey; + protected String privateKeyId = "0"; + protected int roleTokenDefaultTimeout; + protected int roleTokenMaxTimeout; + protected long serviceTokenTimeOffset; + protected long bootTimeOffset; + protected boolean traceAccess = true; + protected long signedPolicyTimeout; + protected String serverHostName = null; + protected AuditLogger auditLogger = null; + protected String auditLoggerMsgBldrClass = null; + protected String serverHttpsPort = null; + protected String serverHttpPort = null; + protected String userDomain = "user"; + protected boolean leastPrivilegePrincipal = false; + protected Set authorizedProxyUsers = null; + + private static final String ATTR_ACCOUNT_ID = "accountId"; + private static final String ATTR_PENDING_TIME = "pendingTime"; + + private static final String TYPE_DOMAIN_NAME = "DomainName"; + private static final String TYPE_SIMPLE_NAME = "SimpleName"; + private static final String TYPE_ENTITY_NAME = "EntityName"; + private static final String TYPE_SERVICE_NAME = "ServiceName"; + private static final String TYPE_INSTANCE_INFO = "InstanceInformation"; + private static final String TYPE_AWS_INSTANCE_INFO = "AWSInstanceInformation"; + private static final String TYPE_AWS_CERT_REQUEST = "AWSCertificateRequest"; + private static final String TYPE_INSTANCE_REFRESH_REQUEST = "InstanceRefreshRequest"; + private static final String TYPE_DOMAIN_METRICS = "DomainMetrics"; + + private static final String ZTS_ROLE_TOKEN_VERSION = "Z1"; + + private static final long ZTS_NTOKEN_DEFAULT_EXPIRY = TimeUnit.SECONDS.convert(2, TimeUnit.HOURS); + private static final long ZTS_NTOKEN_MAX_EXPIRY = TimeUnit.SECONDS.convert(7, TimeUnit.DAYS); + + // HTTP operation types used in metrics + private static final String HTTP_GET = "GET"; + private static final String HTTP_POST = "POST"; + private static final String HTTP_REQUEST = "REQUEST"; + + // domain metrics prefix + private static final String DOM_METRIX_PREFIX = "dom_metric_"; + + private static final Logger LOGGER = LoggerFactory.getLogger(ZTSImpl.class); + + protected Http.AuthorityList authorities = null; + private final ZTSAuthorizer authorizer; + protected static Validator validator; + + public ZTSImpl(String serverHostName, DataStore dataStore, CloudStore cloudStore, SvcCertStore svcCertStore, + Metric metric, PrivateKey privateKey, String privateKeyId, + AuditLogger auditLog, String auditLogMsgBldrClass) { + + this.schema = ZTSSchema.instance(); + validator = new Validator(schema); + + this.dataStore = dataStore; + this.cloudStore = cloudStore; + this.svcCertStore = svcCertStore; + this.metric = metric; + this.privateKey = privateKey; + this.privateKeyId = privateKeyId; + this.serverHostName = serverHostName; + this.userDomain = System.getProperty(ZTSConsts.ZTS_PROP_USER_DOMAIN, "user"); + + auditLogger = auditLog; + auditLoggerMsgBldrClass = auditLogMsgBldrClass; + + // check to see if we want to disable allowing clients to ask for role + // tokens without role name thus violating the least privilege principle + + leastPrivilegePrincipal = Boolean.parseBoolean(System.getProperty(ZTSConsts.ZTS_PROP_LEAST_PRIVILEGE_PRINCIPLE, "false")); + + // Default Role Token timeout is 2 hours. If the client asks for role tokens + // with a min expiry time of 1 hour, the setting of 2 hours allows the client + // to at least cache the tokens for 1 hour. We're going to set the ZTS client's + // min default value to 15 mins so that we can by default cache tokens for + // an hour and 45 minutes. + + long timeout = TimeUnit.SECONDS.convert(2, TimeUnit.HOURS); + this.roleTokenDefaultTimeout = Integer.parseInt(System.getProperty(ZTSConsts.ZTS_PROP_ROLE_TOKEN_DEFAULT_TIMEOUT, Long.toString(timeout))); + + // Max Timeout - 30 days + + timeout = TimeUnit.SECONDS.convert(30, TimeUnit.DAYS); + this.roleTokenMaxTimeout = Integer.parseInt(System.getProperty(ZTSConsts.ZTS_PROP_ROLE_TOKEN_MAX_TIMEOUT, Long.toString(timeout))); + + // signedPolicyTimeout is in milliseconds but the config setting should be in seconds + // to be consistent with other configuration properties + + timeout = TimeUnit.SECONDS.convert(7, TimeUnit.DAYS); + this.signedPolicyTimeout = 1000 * Long.parseLong(System.getProperty(ZTSConsts.ZTS_PROP_SIGNED_POLICY_TIMEOUT, Long.toString(timeout))); + + // bootTimeOffset is in milliseconds but the config setting should be in seconds + // to be consistent with other configuration properties + + timeout = TimeUnit.SECONDS.convert(5, TimeUnit.MINUTES); + this.bootTimeOffset = 1000 * Long.parseLong(System.getProperty(ZTSConsts.ZTS_PROP_AWS_BOOT_TIME_OFFSET, Long.toString(timeout))); + + // when requesting service tokens on behalf of tenants, the provisioner's + // token must be fresh and generated with specified number of seconds + + timeout = TimeUnit.SECONDS.convert(5, TimeUnit.MINUTES); + this.serviceTokenTimeOffset = Long.parseLong(System.getProperty(ZTSConsts.ZTS_PROP_SERVICE_TOKEN_TIME_OFFSET, Long.toString(timeout))); + + serverHttpsPort = System.getProperty(ZTSConsts.ZTS_PROP_HTTPS_PORT, Integer.toString(ZTSConsts.ZTS_HTTPS_PORT_DEFAULT)); + serverHttpPort = System.getProperty(ZTSConsts.ZTS_PROP_HTTP_PORT, Integer.toString(ZTSConsts.ZTS_HTTP_PORT_DEFAULT)); + + // retrieve the list of our authorized proxy users + + String authorizedProxyUserList = System.getProperty(ZTSConsts.ZTS_PROP_AUTHORIZED_PROXY_USERS); + if (authorizedProxyUserList != null) { + authorizedProxyUsers = new HashSet<>(Arrays.asList(authorizedProxyUserList.split(","))); + } + + this.authorizer = new ZTSAuthorizer(dataStore, cloudStore); + } + + public void putAuthorityList(Http.AuthorityList authList) { + authorities = authList; + } + + AuditLogMsgBuilder getAuditLogMsgBuilder(ResourceContext ctx, String domainName, String caller, String method) { + AuditLogMsgBuilder msgBldr; + try { + msgBldr = AuditLogFactory.getMsgBuilder(auditLoggerMsgBldrClass); + } catch (Exception exc) { + LOGGER.error("getAuditLogMsgBuilder: failed to get an AuditLogMsgBuilder. Get the default instead.", exc); + msgBldr = AuditLogFactory.getMsgBuilder(); + } + + // get the where - which means where this server is running + msgBldr.whereIp(serverHostName).whereHttpsPort(serverHttpsPort).whereHttpPort(serverHttpPort); + + msgBldr.whatDomain(domainName).whatApi(caller).whatMethod(method); + + // get the 'who' and set it + // + if (ctx != null) { + Principal princ = ((RsrcCtxWrapper) ctx).principal(); + if (princ != null) { + String unsignedCreds = princ.getUnsignedCredentials(); + if (unsignedCreds == null) { + StringBuilder sb = new StringBuilder(); + sb.append("who-name=").append(princ.getName()); + sb.append(",who-domain=").append(princ.getDomain()); + sb.append(",who-yrn=").append(princ.getYRN()); + List roles = princ.getRoles(); + if (roles != null && roles.size() > 0) { + sb.append(",who-roles=").append(roles.toString()); + } + unsignedCreds = sb.toString(); + } + msgBldr.who(unsignedCreds); + } + + // get the client IP + // + msgBldr.clientIp(ServletRequestUtil.getRemoteAddress(ctx.request())); + } + + return msgBldr; + } + + @Override + public String getPublicKey(String domain, String service, String keyId) { + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case since ZMS Server + // saves all of its object names in lower case + + if (domain != null) { + domain = domain.toLowerCase(); + } + if (service != null) { + service = service.toLowerCase(); + } + if (keyId != null) { + keyId = keyId.toLowerCase(); + } + + return dataStore.getPublicKey(domain, service, keyId); + } + + /** + * @return the ZTS Schema object, describing its API and types. + */ + public Schema schema() { + return schema; + } + + ServiceIdentity generateZTSServiceIdentity(com.yahoo.athenz.zms.ServiceIdentity zmsService) { + + // zms and zts are using the same definition for service identities but + // due to RDL generated code they have different classes. So we're going + // convert our ZMS Service object into a struct and then back to ZTS object + + ServiceIdentity ztsService = new ServiceIdentity() + .setName(zmsService.getName()) + .setExecutable(zmsService.getExecutable()) + .setGroup(zmsService.getGroup()) + .setHosts(zmsService.getHosts()) + .setModified(zmsService.getModified()) + .setProviderEndpoint(zmsService.getProviderEndpoint()) + .setUser(zmsService.getUser()); + List zmsPublicKeys = zmsService.getPublicKeys(); + if (zmsPublicKeys != null) { + ArrayList ztsPublicKeys = new ArrayList<>(); + for (com.yahoo.athenz.zms.PublicKeyEntry zmsPublicKey : zmsPublicKeys) { + PublicKeyEntry ztsPublicKey = new PublicKeyEntry() + .setId(zmsPublicKey.getId()) + .setKey(zmsPublicKey.getKey()); + ztsPublicKeys.add(ztsPublicKey); + } + ztsService.setPublicKeys(ztsPublicKeys); + } + + return ztsService; + } + + String generateServiceIdentityName(String domain, String service) { + StringBuilder str = new StringBuilder(256); + str.append(domain); + str.append("."); + str.append(service); + return str.toString(); + } + + ServiceIdentity lookupServiceIdentity(DomainData domainData, String serviceName) { + + List services = domainData.getServices(); + if (services == null) { + return null; + } + + for (com.yahoo.athenz.zms.ServiceIdentity service : services) { + if (service.getName().equalsIgnoreCase(serviceName)) { + ServiceIdentity ztsService = generateZTSServiceIdentity(service); + return ztsService; + } + } + + return null; + } + + // ----------------- the ServiceIdentity interface + + public ServiceIdentity getServiceIdentity(ResourceContext ctx, String domainName, String serviceName) { + + final String caller = "getserviceidentity"; + final String callerTiming = "getserviceidentity_timing"; + metric.increment(HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(serviceName, TYPE_SIMPLE_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case since ZMS Server + // saves all of its object names in lower case + + domainName = domainName.toLowerCase(); + serviceName = serviceName.toLowerCase(); + + Object timerMetric = metric.startTiming(callerTiming, domainName); + DomainData domainData = dataStore.getDomainData(domainName); + if (domainData == null) { + metric.increment(HTTP_REQUEST, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + metric.increment(caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + throw notFoundError("Domain not found: '" + domainName + "'", caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + + // update our metric with dimension. we're moving the metric here + // after the domain name has been confirmed as valid since with + // dimensions we get stuck with persistent indexes so we only want + // to create them for valid domain names + + metric.increment(HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + + String cnService = generateServiceIdentityName(domainName, serviceName); + ServiceIdentity ztsService = lookupServiceIdentity(domainData, cnService); + + if (ztsService == null) { + throw notFoundError("Service not found: '" + cnService + "'", caller, domainName); + } + + metric.stopTiming(timerMetric); + return ztsService; + } + + public PublicKeyEntry getPublicKeyEntry(ResourceContext ctx, String domainName, String serviceName, String keyId) { + + final String caller = "getpublickeyentry"; + final String callerTiming = "getpublickeyentry_timing"; + metric.increment(HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(serviceName, TYPE_SIMPLE_NAME, caller); + + if (keyId == null) { + throw requestError("Invalid Public Key Id specified", caller, domainName); + } + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domainName = domainName.toLowerCase(); + serviceName = serviceName.toLowerCase(); + keyId = keyId.toLowerCase(); + + Object timerMetric = metric.startTiming(callerTiming, domainName); + metric.increment(HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + String publicKey = dataStore.getPublicKey(domainName, serviceName, keyId); + if (publicKey == null) { + throw notFoundError("Public Key not found", caller, domainName); + } + + PublicKeyEntry entry = new PublicKeyEntry().setId(keyId) + .setKey(Crypto.ybase64(publicKey.getBytes(StandardCharsets.UTF_8))); + metric.stopTiming(timerMetric); + return entry; + } + + void addServiceNameToList(String fullName, String prefix, List names) { + + if (!fullName.startsWith(prefix)) { + return; + } + + names.add(fullName.substring(prefix.length())); + } + + public ServiceIdentityList getServiceIdentityList(ResourceContext ctx, String domainName) { + + final String caller = "getserviceidentitylist"; + final String callerTiming = "getserviceidentitylist_timing"; + metric.increment(HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case since ZMS Server + // saves all of its object names in lower case + + domainName = domainName.toLowerCase(); + Object timerMetric = metric.startTiming(callerTiming, domainName); + + DomainData domainData = dataStore.getDomainData(domainName); + if (domainData == null) { + metric.increment(HTTP_REQUEST, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + metric.increment(caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + throw notFoundError("Domain not found: '" + domainName + "'", caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + + // update our metric with dimension. we're moving the metric here + // after the domain name has been confirmed as valid since with + // dimensions we get stuck with persistent indexes so we only want + // to create them for valid domain names + + metric.increment(HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + + List names = new ArrayList(); + String prefix = domainName + "."; + + ServiceIdentityList result = new ServiceIdentityList(); + List services = domainData.getServices(); + if (services != null) { + for (com.yahoo.athenz.zms.ServiceIdentity service : services) { + addServiceNameToList(service.getName(), prefix, names); + } + result.setNames(names); + } + + metric.stopTiming(timerMetric); + return result; + } + + public HostServices getHostServices(ResourceContext ctx, String host) { + + final String caller = "gethostservices"; + final String callerTiming = "gethostservices_timing"; + metric.increment(HTTP_GET); + metric.increment(HTTP_REQUEST); + metric.increment(caller); + Object timerMetric = metric.startTiming(callerTiming, null); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case since ZMS Server + // saves all of its object names in lower case + + host = host.toLowerCase(); + HostServices result = dataStore.getHostServices(host); + + metric.stopTiming(timerMetric); + return result; + } + + List getPolicyList(DomainData domainData) { + + ArrayList ztsPolicies = new ArrayList<>(); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = domainData.getPolicies(); + if (signedPolicies == null) { + return ztsPolicies; + } + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = signedPolicies.getContents(); + if (domainPolicies == null) { + return ztsPolicies; + } + + List zmsPolicies = domainPolicies.getPolicies(); + if (zmsPolicies == null) { + return ztsPolicies; + } + + for (com.yahoo.athenz.zms.Policy zmsPolicy : zmsPolicies) { + Policy ztsPolicy = new Policy() + .setModified(zmsPolicy.getModified()) + .setName(zmsPolicy.getName()); + + List zmsAssertions = zmsPolicy.getAssertions(); + if (zmsAssertions != null) { + ArrayList ztsAssertions = new ArrayList<>(); + for (com.yahoo.athenz.zms.Assertion zmsAssertion : zmsAssertions) { + Assertion ztsAssertion = new Assertion() + .setAction(zmsAssertion.getAction()) + .setResource(zmsAssertion.getResource()) + .setRole(zmsAssertion.getRole()); + + if (zmsAssertion.getEffect() != null + && zmsAssertion.getEffect() == com.yahoo.athenz.zms.AssertionEffect.DENY) { + ztsAssertion.setEffect(AssertionEffect.DENY); + } else { + ztsAssertion.setEffect(AssertionEffect.ALLOW); + } + ztsAssertions.add(ztsAssertion); + } + ztsPolicy.setAssertions(ztsAssertions); + } + ztsPolicies.add(ztsPolicy); + } + + return ztsPolicies; + } + + public void getDomainSignedPolicyData(ResourceContext context, String domainName, + String matchingTag, GetDomainSignedPolicyDataResult signedPoliciesResult) { + + final String caller = "getdomainsignedpolicydata"; + final String callerTiming = "getdomainsignedpolicydata_timing"; + metric.increment(HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case since ZMS Server + // saves all of its object names in lower case + + domainName = domainName.toLowerCase(); + Object timerMetric = metric.startTiming(callerTiming, domainName); + + DomainData domainData = dataStore.getDomainData(domainName); + if (domainData == null) { + metric.increment(HTTP_REQUEST, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + metric.increment(caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + throw notFoundError("Domain not found: '" + domainName + "'", caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + + // update our metric with dimension. we're moving the metric here + // after the domain name has been confirmed as valid since with + // dimensions we get stuck with persistent indexes so we only want + // to create them for valid domain names + + metric.increment(HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + + Timestamp modified = domainData.getModified(); + EntityTag eTag = new EntityTag(modified.toString()); + String tag = eTag.toString(); + + // Set timestamp for domain rather than youngest policy. + // Since a policy could have been deleted, and can only be detected + // via the domain modified timestamp. + + if (matchingTag != null && matchingTag.equals(tag)) { + signedPoliciesResult.done(304, matchingTag); + } + + // first get our PolicyData object + + PolicyData policyData = new PolicyData() + .setDomain(domainName) + .setPolicies(getPolicyList(domainData)); + + // then get the signed policy data + + Timestamp expires = Timestamp.fromMillis(System.currentTimeMillis() + signedPolicyTimeout); + + SignedPolicyData signedPolicyData = new SignedPolicyData() + .setPolicyData(policyData) + .setExpires(expires) + .setModified(modified) + .setZmsKeyId(domainData.getPolicies().getKeyId()) + .setZmsSignature(domainData.getPolicies().getSignature()); + + String signature = Crypto.sign(SignUtils.asCanonicalString(signedPolicyData), privateKey); + DomainSignedPolicyData result = new DomainSignedPolicyData() + .setSignedPolicyData(signedPolicyData) + .setSignature(signature) + .setKeyId(privateKeyId); + + metric.stopTiming(timerMetric); + signedPoliciesResult.done(200, result, tag); + } + + String convertEmptyStringToNull(String value) { + + if (value != null && value.length() == 0) { + return null; + } else { + return value; + } + } + + long determineTokenTimeout(Integer minExpiryTime, Integer maxExpiryTime) { + + // we're going to default our return value to the default token + // timeout configured in the server + + long tokenTimeout = roleTokenDefaultTimeout; + + if (maxExpiryTime != null && maxExpiryTime > 0) { + + // if our max expiry time is given and it's a positive number then + // we return that value as our result. We're checking and using the + // max value first since that allows the biggest opportunity on the + // client side to cache the token and return on subsequent requests + + tokenTimeout = maxExpiryTime; + + } else if (minExpiryTime != null && minExpiryTime > roleTokenDefaultTimeout) { + + // now we return the min value but only if it's bigger than our + // default value (if the client is looking for a token that's smaller + // than the default timeout, then they would have specified their + // desired smaller value as the max timeout and the first if block + // would have set accordingly. + + tokenTimeout = minExpiryTime; + } + + // however, we're not going to allow the client to ask for unlimited + // tokens so we'll max it out to the server's configured max timeout + + if (tokenTimeout > roleTokenMaxTimeout) { + tokenTimeout = roleTokenMaxTimeout; + } + + return tokenTimeout; + } + + public TenantDomains getTenantDomains(ResourceContext ctx, String providerDomainName, + String userName, String roleName, String serviceName) { + + final String caller = "gettenantdomains"; + final String callerTiming = "gettenantdomains_timing"; + metric.increment(HTTP_GET); + + validate(providerDomainName, TYPE_DOMAIN_NAME, caller); + validate(userName, TYPE_ENTITY_NAME, caller); + if (roleName != null) { + validate(roleName, TYPE_ENTITY_NAME, caller); + } + if (serviceName != null) { + validate(serviceName, TYPE_SERVICE_NAME, caller); + } + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case since ZMS Server + // saves all of its object names in lower case + + providerDomainName = providerDomainName.toLowerCase(); + userName = userName.toLowerCase(); + if (roleName != null) { + roleName = roleName.toLowerCase(); + } + if (serviceName != null) { + serviceName = serviceName.toLowerCase(); + } + + // first retrieve our domain data object from the cache + + Object timerMetric = metric.startTiming(callerTiming, providerDomainName); + DataCache data = dataStore.getDataCache(providerDomainName); + if (data == null) { + // just increment the request counter without any dimension + // we don't want to get persistent indexes for invalid domains + + metric.increment(HTTP_REQUEST, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + metric.increment(caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + LOGGER.error("getTenantDomains: Unknown provider domain: " + providerDomainName); + throw notFoundError("getTenantDomains: No such provider domain: " + providerDomainName, + caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + + // update our metric with dimension. we're moving the metric here + // after the domain name has been confirmed as valid since with + // dimensions we get stuck with persistent indexes so we only want + // to create them for valid domain names + + metric.increment(HTTP_REQUEST, providerDomainName); + metric.increment(caller, providerDomainName); + + // process our request and retrieve the roles for the principal + + ArrayList roles = new ArrayList<>(); + + // if the username does not contain a domain then we'll assume + // user domain and handle accordingly + + if (userName.indexOf('.') == -1) { + userName = this.userDomain + "." + userName; + } + + dataStore.getAccessibleRoles(data, providerDomainName, userName, + roleName, roles, false); + + // we are going to process the list and only keep the tenant + // domains - this is based on the role names since our tenant + // roles are named: .tenant..[.] + + Set domainNames = new HashSet<>(); + for (String role : roles) { + + String domainName = retrieveTenantDomainName(role, serviceName); + if (domainName != null) { + domainNames.add(domainName); + } + } + + TenantDomains tenantDomains = new TenantDomains(); + tenantDomains.setTenantDomainNames(new ArrayList(domainNames)); + + metric.stopTiming(timerMetric); + return tenantDomains; + } + + String retrieveTenantDomainName(String roleName, String serviceName) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("retrieveTenantDomainName: Processing role name: " + roleName); + } + + // roles are named: .tenant..[.] + // so we're going to do the easy checks first + // check 1: we must have at least 4 components + + String[] comps = roleName.split("\\."); + if (comps.length < 4) { + return null; + } + + // check 2: our second component must be the word tenant + + if (!comps[1].equals("tenant")) { + return null; + } + + // check 3: if service name is given, it must be the first component + + if (serviceName != null && !comps[0].equals(serviceName)) { + return null; + } + + // if we have 4 components then component 3 is the domain name + + if (comps.length == 4) { + + // verify it's a valid domain name before returning + + if (dataStore.getDataCache(comps[2]) == null) { + return null; + } + + return comps[2]; + } + + // so if we have more components than 4 then we have two + // choices to deal with: with and without resource groups + // first let's generate into two strings - one assuming + // to be the resource group + + String resourceGroup = comps[comps.length - 2]; + StringBuffer domainNameBuf = new StringBuffer(512).append(comps[2]); + for (int i = 3; i < comps.length - 2; i++) { + domainNameBuf.append('.').append(comps[i]); + } + + // first we're going to assume the resource group as part + // of the domain name and see if that domain exists + + String fullDomainName = domainNameBuf.toString() + "." + resourceGroup; + if (dataStore.getDataCache(fullDomainName) != null) { + return fullDomainName; + } + + // now let's try without the resource group part + + fullDomainName = domainNameBuf.toString(); + if (dataStore.getDataCache(fullDomainName) != null) { + return fullDomainName; + } + + // we didn't have valid domain + + return null; + } + + boolean isAuthorizedProxyUser(Set proxyUsers, String principal) { + if (proxyUsers == null) { + return false; + } + return proxyUsers.contains(principal); + } + + // Token interface + public RoleToken getRoleToken(ResourceContext ctx, String domainName, String roleName, + Integer minExpiryTime, Integer maxExpiryTime, String proxyForPrincipal) { + + final String caller = "getroletoken"; + final String callerTiming = "getroletoken_timing"; + metric.increment(HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + if (roleName != null && !roleName.isEmpty()) { + validate(roleName, TYPE_ENTITY_NAME, caller); + } + if (proxyForPrincipal != null && !proxyForPrincipal.isEmpty()) { + validate(proxyForPrincipal, TYPE_ENTITY_NAME, caller); + } + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case since ZMS Server + // saves all of its object names in lower case + + domainName = domainName.toLowerCase(); + if (roleName != null) { + roleName = roleName.toLowerCase(); + } + + Object timerMetric = metric.startTiming(callerTiming, domainName); + + // get our principal's name + + String principal = ((RsrcCtxWrapper) ctx).principal().getYRN(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("getRoleToken(domain: " + domainName + ", principal: " + principal + + ", role-name: " + roleName + ", proxy-for: " + proxyForPrincipal + ")"); + } + + // do not allow empty (not null) values for role + + roleName = convertEmptyStringToNull(roleName); + proxyForPrincipal = convertEmptyStringToNull(proxyForPrincipal); + + if (leastPrivilegePrincipal && roleName == null) { + LOGGER.error("getRoleToken: Principal: " + principal + + " requested a role token without the required roleName"); + throw requestError("getRoleToken: Client must specify a roleName to request a token for", + caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + + // we can only have a proxy for principal request if the original + // caller is authorized for such operations + + if (proxyForPrincipal != null && !isAuthorizedProxyUser(authorizedProxyUsers, principal)) { + LOGGER.error("getRoleToken: Principal: " + principal + + " not authorized for proxy role token request"); + throw forbiddenError("getRoleToken: Principal: " + principal + + " not authorized for proxy role token request", caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + + StringBuilder auditLogDetails = new StringBuilder(512); + auditLogDetails.append("RoleName=").append(roleName); + AuditLogMsgBuilder msgBldr = getAuditLogMsgBuilder(ctx, domainName, caller, HTTP_GET); + msgBldr.when(Timestamp.fromCurrentTime().toString()). + whatEntity("RoleToken").why("zts-audit"); + + // first retrieve our domain data object from the cache + + DataCache data = dataStore.getDataCache(domainName); + if (data == null) { + // just increment the request counter without any dimension + // we don't want to get persistent indexes for invalid domains + + metric.increment(HTTP_REQUEST, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + metric.increment(caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + + // create our audit log entry + + auditLogDetails.append(",ERROR=(No Such Domain)"); + msgBldr.whatDetails(auditLogDetails.toString()); + if (auditLogger != null) { + auditLogger.log(msgBldr); + } else { + LOGGER.error(msgBldr.toString()); + } + LOGGER.error("getRoleToken: Principal: " + principal + + " requested a role token for an unknown domain: " + domainName); + throw notFoundError("getRoleToken: No such domain: " + domainName, caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + + // update our metric with dimension. we're moving the metric here + // after the domain name has been confirmed as valid since with + // dimensions we get stuck with persistent indexes so we only want + // to create them for valid domain names + + metric.increment(HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + + // process our request and retrieve the roles for the principal + + ArrayList roles = new ArrayList<>(); + dataStore.getAccessibleRoles(data, domainName, principal, roleName, + roles, false); + + if (roles.isEmpty()) { + auditLogDetails.append(",ERROR=(Principal Has No Access to Domain)"); + msgBldr.whatDetails(auditLogDetails.toString()); + if (auditLogger != null) { + auditLogger.log(msgBldr); + } else { + LOGGER.error(msgBldr.toString()); + } + LOGGER.error("getRoleToken: Principal: " + principal + + " has no acccess to any roles in domain: " + domainName); + throw forbiddenError("getRoleToken: No access to any roles in domain: " + domainName, + caller, domainName); + } + + // if this is proxy for operation then we want to make sure that + // both principals have access to the same set of roles otherwise + // we're not going to issue a roletoken with an updated principal + // field + + String proxyUser = null; + if (proxyForPrincipal != null) { + ArrayList rolesForProxy = new ArrayList<>(); + dataStore.getAccessibleRoles(data, domainName, proxyForPrincipal, roleName, rolesForProxy, false); + if (!compareRoleLists(roles, rolesForProxy)) { + throw forbiddenError("getRoleToken: Principal does not have access to the same set of roles as proxy principal", + caller, domainName); + } + + // we need to switch our principal and proxy for user + + proxyUser = principal; + principal = proxyForPrincipal; + } + + long tokenTimeout = determineTokenTimeout(minExpiryTime, maxExpiryTime); + com.yahoo.athenz.auth.token.RoleToken token = + new com.yahoo.athenz.auth.token.RoleToken.Builder(ZTS_ROLE_TOKEN_VERSION, domainName, roles) + .expirationWindow(tokenTimeout).host(serverHostName).keyId(privateKeyId) + .principal(principal).ip(ServletRequestUtil.getRemoteAddress(ctx.request())) + .proxyUser(proxyUser).domainCompleteRoleSet(roleName == null).build(); + token.sign(privateKey); + + RoleToken roleToken = new RoleToken(); + roleToken.setToken(token.getSignedToken()); + roleToken.setExpiryTime(token.getExpiryTime()); + + auditLogDetails.append(",SUCCESS ROLETOKEN=(").append(token.getUnsignedToken()).append(")"); + msgBldr.whatDetails(auditLogDetails.toString()); + if (auditLogger != null) { + auditLogger.log(msgBldr); + } else { + LOGGER.error(msgBldr.toString()); + } + + metric.stopTiming(timerMetric); + return roleToken; + } + + boolean compareRoleLists(List list1, List list2) { + + if (list1.size() != list2.size()) { + LOGGER.error("Role lists do not have the same size: " + list1.size() + " vs. " + list2.size()); + return false; + } + + Set set2 = new HashSet<>(list2); + for (String item : list1) { + if (!set2.contains(item)) { + return false; + } + } + + return true; + } + + public RoleAccess getRoleAccess(ResourceContext ctx, String domainName, String principal) { + + final String caller = "getroleaccess"; + final String callerTiming = "getroleaccess_timing"; + metric.increment(HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(principal, TYPE_ENTITY_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case since ZMS Server + // saves all of its object names in lower case + + domainName = domainName.toLowerCase(); + principal = principal.toLowerCase(); + + Object timerMetric = metric.startTiming(callerTiming, domainName); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("getRoleAccess(domain: " + domainName + ", principal: " + principal + ")"); + } + + // first retrieve our domain data object from the cache + + DataCache data = dataStore.getDataCache(domainName); + if (data == null) { + // just increment the request counter without any dimension + // we don't want to get persistent indexes for invalid domains + + metric.increment(HTTP_REQUEST, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + metric.increment(caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + + LOGGER.error("getRoleAccess: Principal: " + principal + + " requested role access for an unknown domain: " + domainName); + throw notFoundError("getRoleAccess: No such domain: " + domainName, + caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + + // update our metric with dimension. we're moving the metric here + // after the domain name has been confirmed as valid since with + // dimensions we get stuck with persistent indexes so we only want + // to create them for valid domain names + + metric.increment(HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + + // process our request and retrieve the roles for the principal + + ArrayList roles = new ArrayList<>(); + dataStore.getAccessibleRoles(data, domainName, principal, null, + roles, false); + + RoleAccess roleAccess = new RoleAccess().setRoles(roles); + metric.stopTiming(timerMetric); + return roleAccess; + } + + public AWSTemporaryCredentials getAWSTemporaryCredentials(ResourceContext ctx, String domainName, + String roleName) { + + final String caller = "getawstemporarycredentials"; + final String callerTiming = "getawstemporarycredentials_timing"; + metric.increment(HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(roleName, TYPE_ENTITY_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case since ZMS Server + // saves all of its object names in lower case + + domainName = domainName.toLowerCase(); + roleName = roleName.toLowerCase(); + + Object timerMetric = metric.startTiming(callerTiming, domainName); + metric.increment(HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("getAWSTemporaryCredentials(domain: " + domainName + ", role: " + roleName + ")"); + } + + if (!cloudStore.isAwsEnabled()) { + throw requestError("getAWSTemporaryCredentials: AWS support is not available", + caller, domainName); + } + + // get our principal's name + + String principal = ((RsrcCtxWrapper) ctx).principal().getYRN(); + + String roleResource = domainName + ":" + roleName; + + // we need to first verify that our principal is indeed configured + // with aws assume role assertion for the specified role and domain + + if (!verifyAWSAssumeRole(domainName, roleResource, principal)) { + throw forbiddenError("getAWSTemporaryCredentials: Forbidden (ASSUME_AWS_ROLE on " + + roleResource + " for " + principal + ")", caller, domainName); + } + + // now need to get the associated AWS account for the domain name + + String account = cloudStore.getAWSAccount(domainName); + if (account == null) { + throw requestError("getAWSTemporaryCredentials: unable to retrieve AWS account for: " + + domainName, caller, domainName); + } + + // obtain the credentials from the cloud store + + AWSTemporaryCredentials creds = cloudStore.assumeAWSRole(account, roleName, principal); + if (creds == null) { + throw requestError("getAWSTemporaryCredentials: unable to assume role " + roleName + + " in domain " + domainName + " for principal " + principal, caller, domainName); + } + + metric.stopTiming(timerMetric); + return creds; + } + + boolean verifyAWSAssumeRole(String domainName, String roleResource, String principal) { + + // first retrieve our domain data object from the cache + + DataCache data = dataStore.getDataCache(domainName); + if (data == null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("verifyAWSAssumeRole: unknown domain: " + domainName); + } + return false; + } + + // retrieve the roles for the principal + + ArrayList roles = new ArrayList<>(); + dataStore.getAccessibleRoles(data, domainName, principal, null, roles, true); + + if (roles.isEmpty()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("verifyAWSAssumeRole: Principal: " + principal + + " has no acccess to any roles in domain: " + domainName); + } + return false; + } + + // check to see if any of the roles give access to the specified resource + + Set awsResourceSet = null; + for (String role : roles) { + awsResourceSet = data.getAWSResourceRoleSet(role); + if (awsResourceSet != null && awsResourceSet.contains(roleResource)) { + return true; + } + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("verifyAWSAssumeRole: Principal: " + principal + + " has no acccess to resource: " + roleResource + " in domain: " + domainName); + } + + return false; + } + + @Override + public Identity postAWSInstanceInformation(ResourceContext context, AWSInstanceInformation info) { + + final String caller = "postawsinstanceinformation"; + final String callerTiming = "postawsinstanceinformation_timing"; + metric.increment(HTTP_POST); + + validate(info, TYPE_AWS_INSTANCE_INFO, caller); + + Object timerMetric = metric.startTiming(callerTiming, info.getDomain()); + metric.increment(HTTP_REQUEST, info.getDomain()); + metric.increment(caller, info.getDomain()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("postAWSInstanceInformation: " + info); + } + + if (!cloudStore.isAwsEnabled()) { + throw requestError("postAWSInstanceInformation: AWS support is not available", + caller, info.getDomain()); + } + + // verify we have a valid aws enabled domain + + String account = cloudStore.getAWSAccount(info.getDomain()); + if (account == null) { + throw requestError("postAWSInstanceInformation: unable to retrieve AWS account for: " + + info.getDomain(), caller, info.getDomain()); + } + + // verify the domain account and the account in the info + // object do match + + if (!account.equalsIgnoreCase(info.getAccount())) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("postAWSInstanceInformation: ZTS domain account lookup: " + account); + LOGGER.debug("postAWSInstanceInformation: Instance Information account: " + info.getAccount()); + } + throw requestError("postAWSInstanceInformation: mismatch between account values: " + + " domain lookup: " + account + " vs. instance info: " + info.getAccount(), + caller, info.getDomain()); + } + + // we need to validate the instance document + + if (!cloudStore.validateInstanceDocument(info.getDocument(), info.getSignature())) { + throw requestError("postAWSInstanceInformation: unable to validate instance document", + caller, info.getDomain()); + } + + // convert our document into a struct that we can extract data + + Struct instanceDocument = null; + try { + instanceDocument = JSON.fromString(info.getDocument(), Struct.class); + } catch (Exception ex) { + LOGGER.error("postAWSInstanceInformation: failed to parse: " + info.getDocument() + + " error: " + ex.getMessage()); + } + + if (instanceDocument == null) { + throw requestError("postAWSInstanceInformation: unable to parse instance document", + caller, info.getDomain()); + } + + // verify that the account lookup and the account in the document match + + String docAccount = instanceDocument.getString(ATTR_ACCOUNT_ID); + if (!account.equalsIgnoreCase(docAccount)) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("postAWSInstanceInformation: ZTS domain account lookup: " + account); + LOGGER.debug("postAWSInstanceInformation: Instance document account: " + docAccount); + } + throw requestError("postAWSInstanceInformation: mismatch between account values: " + + " domain lookup: " + account + " vs. instance document: " + docAccount, + caller, info.getDomain()); + } + + // verify that the boot up time for the instance is now + + Timestamp bootTime = instanceDocument.getTimestamp(ATTR_PENDING_TIME); + if (bootTime.millis() < System.currentTimeMillis() - bootTimeOffset) { + throw forbiddenError("postAWSInstanceInformation: instance boot time is not recent enough", + caller, info.getDomain()); + } + + // verify that the temporary credentials specified in the request + // can be used to assume the given role thus verifying the + // instance identity + + if (!cloudStore.verifyInstanceIdentity(info)) { + throw requestError("postAWSInstanceInformation: unable to verify instance identity", + caller, info.getDomain()); + } + + // now let's validate the csr given to us by the client + // and generate certificate for the instance + + Identity identity = cloudStore.generateIdentity(info.getCsr(), info.getName()); + if (identity == null) { + throw requestError("postAWSInstanceInformation: unable to generate identity", + caller, info.getDomain()); + } + + metric.stopTiming(timerMetric); + return identity; + } + + @Override + public Identity postInstanceRefreshRequest(ResourceContext ctx, String domain, + String service, InstanceRefreshRequest req) { + + final String caller = "postinstancerefreshrequest"; + final String callerTiming = "postinstancerefreshrequest_timing"; + metric.increment(HTTP_POST); + + validate(domain, TYPE_DOMAIN_NAME, caller); + validate(service, TYPE_SIMPLE_NAME, caller); + validate(req, TYPE_INSTANCE_REFRESH_REQUEST, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domain = domain.toLowerCase(); + service = service.toLowerCase(); + + Object timerMetric = metric.startTiming(callerTiming, domain); + metric.increment(HTTP_REQUEST, domain); + metric.increment(caller, domain); + + // make sure the credentials match to whatever the request is + + Principal principal = ((RsrcCtxWrapper) ctx).principal(); + String yrn = domain + "." + service; + if (!yrn.equals(principal.getYRN())) { + throw requestError("postInstanceRefreshRequest: Principal mismatch: " + + yrn + " vs. " + principal.getYRN(), caller, domain); + } + + Identity identity = null; + if (principal.getAuthority() instanceof CertificateAuthority) { + identity = svcCertStore.generateIdentity(req.getCsr(), yrn); + } + + if (identity == null) { + throw requestError("postInstanceRefreshRequest: unable to generate identity", caller, domain); + } + + metric.stopTiming(timerMetric); + return identity; + } + + @Override + public Identity postInstanceInformation(ResourceContext context, InstanceInformation info) { + final String caller = "postinstanceinformation"; + final String callerTiming = "postinstanceinformation_timing"; + metric.increment(HTTP_POST); + + String domain = info.getDomain(); + String service = info.getService(); + + Object timerMetric = metric.startTiming(callerTiming, domain); + metric.increment(HTTP_REQUEST, domain); + metric.increment(caller, domain); + + validate(info, TYPE_INSTANCE_INFO, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domain = domain.toLowerCase(); + service = service.toLowerCase(); + + // now let's validate the request, and the csr, given to us by the client + // and generate certificate for the instance + + if (!svcCertStore.isValidRequest(info)) { + throw requestError("postInstanceInformation: unable to generate identity, invalid request", caller, domain); + } + + Identity identity = svcCertStore.generateIdentity(info.getCsr(), domain + "." + service); + if (identity == null) { + throw requestError("postInstanceInformation: unable to generate identity", + caller, domain); + } + + metric.stopTiming(timerMetric); + + return identity; + } + + long getSvcTokenExpiryTime(Integer expiryTime) { + + long requestedValue = (expiryTime != null) ? expiryTime : ZTS_NTOKEN_DEFAULT_EXPIRY; + if (requestedValue <= 0) { + requestedValue = ZTS_NTOKEN_DEFAULT_EXPIRY; + } else if (requestedValue > ZTS_NTOKEN_MAX_EXPIRY) { + requestedValue = ZTS_NTOKEN_MAX_EXPIRY; + } + + return requestedValue; + } + + @Override + public Identity postAWSCertificateRequest(ResourceContext ctx, String domain, String service, + AWSCertificateRequest req) { + + final String caller = "postawscertificaterequest"; + final String callerTiming = "postawscertificaterequest_timing"; + metric.increment(HTTP_POST); + + validate(domain, TYPE_DOMAIN_NAME, caller); + validate(service, TYPE_SIMPLE_NAME, caller); + validate(req, TYPE_AWS_CERT_REQUEST, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case (e.g. domain, role, + // policy, service, etc name) + + domain = domain.toLowerCase(); + service = service.toLowerCase(); + + Object timerMetric = metric.startTiming(callerTiming, domain); + metric.increment(HTTP_REQUEST, domain); + metric.increment(caller, domain); + + // get our principal's name + + // make sure this was authenticated by the + // Certificate authority and not by anyone else + + Principal principal = ((RsrcCtxWrapper) ctx).principal(); + Authority authority = principal.getAuthority(); + + if (!(authority instanceof com.yahoo.athenz.auth.impl.CertificateAuthority)) { + throw forbiddenError("postAWSCertificateRequest: Not authenticated by Certificate Authority", + caller, domain); + } + + String principalYrn = principal.getYRN(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("postAWSCertificateRequest: " + req + " for principal: " + principal); + } + + if (!cloudStore.isAwsEnabled()) { + throw requestError("postAWSCertificateRequest: AWS support is not available", + caller, domain); + } + + // verify we have a valid aws enabled domain + + String account = cloudStore.getAWSAccount(domain); + if (account == null) { + throw requestError("postAWSCertificateRequest: unable to retrieve AWS account for: " + + domain, caller, domain); + } + + // now let's validate the csr given to us by the client + // and generate certificate for the instance + + Identity identity = cloudStore.generateIdentity(req.getCsr(), principalYrn); + if (identity == null) { + throw requestError("postAWSCertificateRequest: unable to generate identity", + caller, domain); + } + + metric.stopTiming(timerMetric); + return identity; + } + + @Override + public Access getAccess(ResourceContext context, String domainName, String roleName, String principal) { + + final String caller = "getaccess"; + final String callerTiming = "getaccess_timing"; + metric.increment(HTTP_GET); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(roleName, TYPE_ENTITY_NAME, caller); + validate(principal, TYPE_ENTITY_NAME, caller); + + // for consistent handling of all requests, we're going to convert + // all incoming object values into lower case since ZMS Server + // saves all of its object names in lower case + + domainName = domainName.toLowerCase(); + roleName = roleName.toLowerCase(); + principal = principal.toLowerCase(); + + Object timerMetric = metric.startTiming(callerTiming, domainName); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("getAccess(domain: " + domainName + ", principal: " + principal + + ", role: " + roleName + ")"); + } + + // first retrieve our domain data object from the cache + + DataCache data = dataStore.getDataCache(domainName); + if (data == null) { + // just increment the request counter without any dimension + // we don't want to get persistent indexes for invalid domains + + metric.increment(HTTP_REQUEST, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + metric.increment(caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + + LOGGER.error("getAccess(principal: " + principal + ", role: " + roleName + + ") unknown domain: " + domainName); + throw notFoundError("getAccess: No such domain: " + domainName, caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + + // update our metric with dimension. we're moving the metric here + // after the domain name has been confirmed as valid since with + // dimensions we get stuck with persistent indexes so we only want + // to create them for valid domain names + + metric.increment(HTTP_REQUEST, domainName); + metric.increment(caller, domainName); + + // process our request and retrieve the roles for the principal + + ArrayList roles = new ArrayList<>(); + dataStore.getAccessibleRoles(data, domainName, principal, null, + roles, false); + + // create our response object and set the flag whether + // or not the principal has access to the role + + Access access = new Access(); + access.setGranted(roles.contains(roleName)); + + metric.stopTiming(timerMetric); + return access; + } + + /* + * /metrics/{domainName} + */ + @Override + public DomainMetrics postDomainMetrics(ResourceContext context, String domainName, DomainMetrics req) { + final String caller = "postdomainmetrics"; + final String callerTiming = "postdomainmetrics_timing"; + metric.increment(HTTP_POST); + + validate(domainName, TYPE_DOMAIN_NAME, caller); + validate(req, TYPE_DOMAIN_METRICS, caller); + domainName = domainName.toLowerCase(); + + Object timerMetric = metric.startTiming(callerTiming, domainName); + + // verify valid domain specified + DataCache data = dataStore.getDataCache(domainName); + if (data == null) { + metric.increment(HTTP_REQUEST, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + metric.increment(caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + LOGGER.error("postDomainMetrics: request for unknown domain: " + domainName); + throw notFoundError("postDomainMetrics: No such domain: " + domainName, caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + + // verify domain name matches domain name in request object + String metricDomain = req.getDomainName(); + if (metricDomain == null) { + String errMsg = "postDomainMetrics: metrics request missing domain name for uri specified domain: " + domainName; + LOGGER.error(errMsg); + throw requestError(errMsg, caller, domainName); + } else if (metricDomain != null) { + metricDomain = metricDomain.toLowerCase(); + if (!metricDomain.equals(domainName)) { + String errMsg = "postDomainMetrics: mismatched domain names: uri domain: " + domainName + " : metric domain: " + metricDomain; + LOGGER.error(errMsg); + throw requestError(errMsg, caller, domainName); + } + } + + List dmList = req.getMetricList(); + if (dmList == null || dmList.size() == 0) { + // no metrics were sent - log error + String errMsg = "postDomainMetrics: received no metrics for domain: " + domainName; + LOGGER.error(errMsg); + throw requestError(errMsg, caller, domainName); + } + + // process the DomainMetrics request in order to increment each of its attrs + for (DomainMetric dm: dmList) { + DomainMetricType dmType = dm.getMetricType(); + if (dmType == null) { + LOGGER.warn("postDomainMetrics: ignore missing metric received for domain: {}", domainName); + continue; + } + + String dmt = dmType.toString().toLowerCase(); + Integer count = dm.getMetricVal(); + if (count == null || count.intValue() < 0) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("postDomainMetrics: ignore metric: " + dmt + + " : invalid counter: " + count + " : received for domain: " + domainName); + } + continue; + } + String metricName = DOM_METRIX_PREFIX + dmt; + metric.increment(metricName, domainName, count); + } + + metric.stopTiming(timerMetric); + return req; + } + + @Override + public Schema getRdlSchema(ResourceContext context) { + return schema; + } + + protected String formatValidationError(String msg, Struct v) { + return msg + ": " + v.getString("error") + " [" + v.getString("context") + "]"; + } + + void validate(Object val, String type, String caller) { + if (val == null) { + throw requestError("Missing or malformed " + type, caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + + Result result = validator.validate(val, type); + if (!result.valid) { + throw requestError("Invalid " + type + " error: " + result.error, caller, + ZTSConsts.ZTS_UNKNOWN_DOMAIN); + } + } + + protected RuntimeException error(int code, String msg, String caller, String domainName) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(msg); + } + + // If caller is null, we do not want to emit any error metrics. + // Otherwise, the caller name should be from the method that threw + // the specific runtime exception. + + if (caller != null && !ZTSUtils.emitMonmetricError(code, caller, domainName, this.metric)) { + LOGGER.error("ZTS Error: unable to emit error metric for caller: " + caller + + " with message: " + msg); + } + return new ResourceException(code, new ResourceError().code(code).message(msg)); + } + + protected RuntimeException requestError(String msg, String caller, String domainName) { + return error(ResourceException.BAD_REQUEST, msg, caller, domainName); + } + + protected RuntimeException forbiddenError(String msg, String caller, String domainName) { + return error(ResourceException.FORBIDDEN, msg, caller, domainName); + } + + protected RuntimeException notFoundError(String msg, String caller, String domainName) { + return error(ResourceException.NOT_FOUND, msg, caller, domainName); + } + + + public ResourceContext newResourceContext(HttpServletRequest request, HttpServletResponse response) { + return new RsrcCtxWrapper(request, response, authorities, authorizer); + } + + public Authorizer getAuthorizer() { + return authorizer; + } + +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSJettyContainer.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSJettyContainer.java new file mode 100644 index 00000000000..8317bbb167d --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSJettyContainer.java @@ -0,0 +1,225 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import java.io.File; + +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.rewrite.handler.HeaderPatternRule; +import org.eclipse.jetty.rewrite.handler.RewriteHandler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.Slf4jRequestLog; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.handler.RequestLogHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import org.glassfish.jersey.servlet.ServletContainer; + +import com.yahoo.athenz.common.server.filters.DefaultMediaTypeFilter; +import com.yahoo.athenz.common.server.log.AthenzRequestLog; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.common.server.rest.Http; +import com.yahoo.athenz.common.server.rest.HttpContainer; +import com.yahoo.athenz.common.server.rest.RestCoreResourceConfig; +import com.yahoo.athenz.zts.utils.ZTSUtils; + +public class ZTSJettyContainer extends HttpContainer { + + /** + * This simple Container runs Jersey in a standalone mode using the Jetty + * HTTP server, and the ContentTypeProvider that can read and write payloads + * using any of data-core's Codec implementations (i.e. JSON, + * ProtocolBuffers, Avro, and TBin), just by identifying the Content-type or + * Accept headers in your request. + */ + + AuditLogger auditLogger = null; + String auditLoggerMsgBldrClass = null; + + public ZTSJettyContainer(AuditLogger auditLog, String auditLogMsgBldrClass) { + auditLogger = auditLog; + auditLoggerMsgBldrClass = auditLogMsgBldrClass; + + authorities = new Http.AuthorityList(); + } + + public void addRequestLogHandler(String rootDir) { + + RequestLogHandler requestLogHandler = new RequestLogHandler(); + + // check to see if have a slf4j logger name specified. if we don't + // then we'll just use our NCSARequestLog extended Athenz logger + // when using the slf4j logger we don't have the option to pass + // our audit logger to keep track of unauthenticated requests + + String accessSlf4jLogger = System.getProperty(ZTSConsts.ZTS_PROP_ACCESS_SLF4J_LOGGER); + if (accessSlf4jLogger != null && !accessSlf4jLogger.isEmpty()) { + + Slf4jRequestLog requestLog = new Slf4jRequestLog(); + requestLog.setLoggerName(accessSlf4jLogger); + requestLog.setExtended(true); + requestLog.setPreferProxiedForAddress(true); + requestLog.setLogTimeZone("GMT"); + requestLogHandler.setRequestLog(requestLog); + + } else { + + String logDir = System.getProperty(ZTSConsts.ZTS_PROP_ACCESS_LOG_DIR, rootDir + "/logs/zts_server"); + String logName = System.getProperty(ZTSConsts.ZTS_PROP_ACCESS_LOG_NAME, "access.yyyy_MM_dd.log"); + + AthenzRequestLog requestLog = new AthenzRequestLog(logDir + File.separator + logName, auditLogger); + requestLog.setAppend(true); + requestLog.setExtended(true); + requestLog.setPreferProxiedForAddress(true); + requestLog.setLogTimeZone("GMT"); + + String retainDays = System.getProperty(ZTSConsts.ZTS_PROP_ACCESS_LOG_RETAIN_DAYS, "31"); + int days = Integer.parseInt(retainDays); + if (days > 0) { + requestLog.setRetainDays(days); + } + requestLogHandler.setRequestLog(requestLog); + } + + getHandlers().addHandler(requestLogHandler); + } + + public void addServletHandlers(String homeDir, String serverHostName) { + + RewriteHandler rewriteHandler = new RewriteHandler(); + + // Check whether or not to disable Keep-Alive support in Jetty. + // This will be the first handler in our array so we always set + // the appropriate header in response. However, since we're now + // behind ATS, we want to keep the connections alive so ATS + // can re-use them as necessary + + boolean keepAlive = Boolean.parseBoolean(System.getProperty(ZTSConsts.ZTS_PROP_KEEP_ALIVE, "true")); + + if (!keepAlive) { + HeaderPatternRule disableKeepAliveRule = new HeaderPatternRule(); + disableKeepAliveRule.setPattern("/*"); + disableKeepAliveRule.setName(HttpHeader.CONNECTION.asString()); + disableKeepAliveRule.setValue(HttpHeaderValue.CLOSE.asString()); + rewriteHandler.addRule(disableKeepAliveRule); + } + + // Return a Host field in the response so during debugging + // we know what server was handling request + + HeaderPatternRule hostNameRule = new HeaderPatternRule(); + hostNameRule.setPattern("/*"); + hostNameRule.setName(HttpHeader.HOST.asString()); + hostNameRule.setValue(serverHostName); + rewriteHandler.addRule(hostNameRule); + + getHandlers().addHandler(rewriteHandler); + + // this sets up the default return media type when client accepts + // any type of media + addContainerRequestFilter(DefaultMediaTypeFilter.class); + + // setup application configuration for authorities, content-providers + // et al + RestCoreResourceConfig rconf = new RestCoreResourceConfig(resources, singletons); + rconf.setAuthorityObject(com.yahoo.athenz.auth.Authorizer.class, authorizer); + rconf.setAuthorityObject(Http.AuthorityList.class, authorities); + rconf.registerAll(); + + // now setup our servlet handler + // + ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); + servletHandler.setContextPath("/"); + + ServletHolder holder = new ServletHolder(new ServletContainer(rconf)); + servletHandler.addServlet(holder, "/*"); + + getHandlers().addHandler(servletHandler); + } + + public HttpConfiguration newHttpConfiguration(int httpsPort) { + + // HTTP Configuration + + HttpConfiguration httpConfig = new HttpConfiguration(); + + boolean sendServerVersion = Boolean.parseBoolean(System.getProperty(ZTSConsts.ZTS_PROP_SEND_SERVER_VERSION, "false")); + boolean sendDateHeader = Boolean.parseBoolean(System.getProperty(ZTSConsts.ZTS_PROP_SEND_DATE_HEADER, "false")); + int outputBufferSize = Integer.parseInt(System.getProperty(ZTSConsts.ZTS_PROP_OUTPUT_BUFFER_SIZE, "32768")); + int requestHeaderSize = Integer.parseInt(System.getProperty(ZTSConsts.ZTS_PROP_REQUEST_HEADER_SIZE, "8192")); + int responseHeaderSize = Integer.parseInt(System.getProperty(ZTSConsts.ZTS_PROP_RESPONSE_HEADER_SIZE, "8192")); + + if (httpsPort > 0) { + httpConfig.setSecureScheme("https"); + httpConfig.setSecurePort(httpsPort); + } + + httpConfig.setOutputBufferSize(outputBufferSize); + httpConfig.setRequestHeaderSize(requestHeaderSize); + httpConfig.setResponseHeaderSize(responseHeaderSize); + httpConfig.setSendServerVersion(sendServerVersion); + httpConfig.setSendDateHeader(sendDateHeader); + + return httpConfig; + } + + public void addHTTPConnectors(HttpConfiguration httpConfig, int httpPort, int httpsPort) { + + int idleTimeout = Integer.parseInt(System.getProperty(ZTSConsts.ZTS_PROP_IDLE_TIMEOUT, "30000")); + String listenHost = System.getProperty(ZTSConsts.ZTS_PROP_LISTEN_HOST); + + // HTTP Connector + + if (httpPort > 0) { + ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); + if (listenHost != null) { + connector.setHost(listenHost); + } + connector.setPort(httpPort); + connector.setIdleTimeout(idleTimeout); + server.addConnector(connector); + } + + // SSL Context Factory + + if (httpsPort > 0) { + + SslContextFactory sslContextFactory = ZTSUtils.createSSLContextObject(null); + + // SSL HTTP Configuration + + HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); + httpsConfig.addCustomizer(new SecureRequestCustomizer()); + + // SSL Connector + + ServerConnector sslConnector = new ServerConnector(server, + new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), + new HttpConnectionFactory(httpsConfig)); + sslConnector.setPort(httpsPort); + sslConnector.setIdleTimeout(idleTimeout); + server.addConnector(sslConnector); + } + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSResources.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSResources.java new file mode 100644 index 00000000000..4b83bcf6d8d --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSResources.java @@ -0,0 +1,428 @@ +// +// This file generated by rdl 1.4.8. Do not modify! +// +package com.yahoo.athenz.zts; + +import com.yahoo.rdl.*; +import java.util.*; +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.inject.Inject; + +@Path("/zts/v1") +public class ZTSResources { + + @GET + @Path("/domain/{domainName}/service/{serviceName}") + @Produces(MediaType.APPLICATION_JSON) + public ServiceIdentity getServiceIdentity(@PathParam("domainName") String domainName, @PathParam("serviceName") String serviceName) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + ServiceIdentity e = this.delegate.getServiceIdentity(context, domainName, serviceName); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getServiceIdentity"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/service") + @Produces(MediaType.APPLICATION_JSON) + public ServiceIdentityList getServiceIdentityList(@PathParam("domainName") String domainName) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + ServiceIdentityList e = this.delegate.getServiceIdentityList(context, domainName); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getServiceIdentityList"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/service/{serviceName}/publickey/{keyId}") + @Produces(MediaType.APPLICATION_JSON) + public PublicKeyEntry getPublicKeyEntry(@PathParam("domainName") String domainName, @PathParam("serviceName") String serviceName, @PathParam("keyId") String keyId) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + PublicKeyEntry e = this.delegate.getPublicKeyEntry(context, domainName, serviceName, keyId); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getPublicKeyEntry"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/host/{host}/services") + @Produces(MediaType.APPLICATION_JSON) + public HostServices getHostServices(@PathParam("host") String host) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + HostServices e = this.delegate.getHostServices(context, host); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getHostServices"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/signed_policy_data") + @Produces(MediaType.APPLICATION_JSON) + public void getDomainSignedPolicyData(@PathParam("domainName") String domainName, @HeaderParam("If-None-Match") String matchingTag) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + GetDomainSignedPolicyDataResult result = new GetDomainSignedPolicyDataResult(context); + this.delegate.getDomainSignedPolicyData(context, domainName, matchingTag, result); + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.NOT_MODIFIED: + throw typedException(code, e, void.class); + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getDomainSignedPolicyData"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/token") + @Produces(MediaType.APPLICATION_JSON) + public RoleToken getRoleToken(@PathParam("domainName") String domainName, @QueryParam("role") String role, @QueryParam("minExpiryTime") Integer minExpiryTime, @QueryParam("maxExpiryTime") Integer maxExpiryTime, @QueryParam("proxyForPrincipal") String proxyForPrincipal) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + RoleToken e = this.delegate.getRoleToken(context, domainName, role, minExpiryTime, maxExpiryTime, proxyForPrincipal); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getRoleToken"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/access/domain/{domainName}/role/{roleName}/principal/{principal}") + @Produces(MediaType.APPLICATION_JSON) + public Access getAccess(@PathParam("domainName") String domainName, @PathParam("roleName") String roleName, @PathParam("principal") String principal) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Access e = this.delegate.getAccess(context, domainName, roleName, principal); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getAccess"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/access/domain/{domainName}/principal/{principal}") + @Produces(MediaType.APPLICATION_JSON) + public RoleAccess getRoleAccess(@PathParam("domainName") String domainName, @PathParam("principal") String principal) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + RoleAccess e = this.delegate.getRoleAccess(context, domainName, principal); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getRoleAccess"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/providerdomain/{providerDomainName}/user/{userName}") + @Produces(MediaType.APPLICATION_JSON) + public TenantDomains getTenantDomains(@PathParam("providerDomainName") String providerDomainName, @PathParam("userName") String userName, @QueryParam("roleName") String roleName, @QueryParam("serviceName") String serviceName) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + TenantDomains e = this.delegate.getTenantDomains(context, providerDomainName, userName, roleName, serviceName); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getTenantDomains"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @POST + @Path("/instance") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Identity postInstanceInformation(InstanceInformation info) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + Identity e = this.delegate.postInstanceInformation(context, info); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource postInstanceInformation"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @POST + @Path("/instance/{domain}/{service}/refresh") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Identity postInstanceRefreshRequest(@PathParam("domain") String domain, @PathParam("service") String service, InstanceRefreshRequest req) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Identity e = this.delegate.postInstanceRefreshRequest(context, domain, service, req); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource postInstanceRefreshRequest"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @POST + @Path("/aws/instance") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Identity postAWSInstanceInformation(AWSInstanceInformation info) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + Identity e = this.delegate.postAWSInstanceInformation(context, info); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource postAWSInstanceInformation"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @POST + @Path("/aws/instance/{domain}/{service}/refresh") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Identity postAWSCertificateRequest(@PathParam("domain") String domain, @PathParam("service") String service, AWSCertificateRequest req) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + Identity e = this.delegate.postAWSCertificateRequest(context, domain, service, req); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource postAWSCertificateRequest"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/domain/{domainName}/role/{role}/creds") + @Produces(MediaType.APPLICATION_JSON) + public AWSTemporaryCredentials getAWSTemporaryCredentials(@PathParam("domainName") String domainName, @PathParam("role") String role) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + context.authenticate(); + AWSTemporaryCredentials e = this.delegate.getAWSTemporaryCredentials(context, domainName, role); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getAWSTemporaryCredentials"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @POST + @Path("/metrics/{domainName}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public DomainMetrics postDomainMetrics(@PathParam("domainName") String domainName, DomainMetrics req) { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + DomainMetrics e = this.delegate.postDomainMetrics(context, domainName, req); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + case ResourceException.BAD_REQUEST: + throw typedException(code, e, ResourceError.class); + case ResourceException.FORBIDDEN: + throw typedException(code, e, ResourceError.class); + case ResourceException.NOT_FOUND: + throw typedException(code, e, ResourceError.class); + case ResourceException.UNAUTHORIZED: + throw typedException(code, e, ResourceError.class); + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource postDomainMetrics"); + throw typedException(code, e, ResourceError.class); + } + } + } + + @GET + @Path("/schema") + @Produces(MediaType.APPLICATION_JSON) + public Schema getRdlSchema() { + try { + ResourceContext context = this.delegate.newResourceContext(this.request, this.response); + Schema e = this.delegate.getRdlSchema(context); + return e; + } catch (ResourceException e) { + int code = e.getCode(); + switch (code) { + default: + System.err.println("*** Warning: undeclared exception (" + code + ") for resource getRdlSchema"); + throw typedException(code, e, ResourceError.class); + } + } + } + + + WebApplicationException typedException(int code, ResourceException e, Class eClass) { + Object data = e.getData(); + Object entity = eClass.isInstance(data) ? data : null; + if (entity != null) { + return new WebApplicationException(Response.status(code).entity(entity).build()); + } else { + return new WebApplicationException(code); + } + } + + @Inject private ZTSHandler delegate; + @Context private HttpServletRequest request; + @Context private HttpServletResponse response; + +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/cache/DataCache.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/cache/DataCache.java new file mode 100644 index 00000000000..3e2e1330be5 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/cache/DataCache.java @@ -0,0 +1,308 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cache; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.auth.util.CryptoException; +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.DomainData; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.PublicKeyEntry; +import com.yahoo.athenz.zms.Role; + +public class DataCache { + + DomainData domainData = null; + + // member ==> [ role1, role2, ...] complete map + private final Map> memberRoleCache; + private final Map> trustCache; + private final Map> hostCache; + private final Map> awsRoleCache; + + // list of roles and their users: role ==> [ member1, member2, ...] complete map + private final Map> roleMemberCache; + + private final Map publicKeyCache; + + public static final String ACTION_ASSUME_ROLE = "assume_role"; + private static final String ACTION_ASSUME_AWS_ROLE = "assume_aws_role"; + + private static final Logger LOGGER = LoggerFactory.getLogger(DataCache.class); + + // rolecache --> memberRoleCache + + public DataCache() { + memberRoleCache = new HashMap<>(); + trustCache = new HashMap<>(); + hostCache = new HashMap<>(); + awsRoleCache = new HashMap<>(); + publicKeyCache = new HashMap<>(); + roleMemberCache = new HashMap<>(); + } + + public void setDomainData(DomainData domainData) { + this.domainData = domainData; + } + + public DomainData getDomainData() { + return domainData; + } + + /** + * Update {@code memberRoleCache} and {@code roleMemberCache} + * @param roleName the new/updated role + * @param members the list of members of that role + */ + void processRoleMembers(String roleName, List members) { + + // roleMemberCache: add role to list of keys, if missing + + if (!roleMemberCache.containsKey(roleName)) { + roleMemberCache.put(roleName, new HashSet<>()); + } + + // early out + + if (members == null) { + return; + } + + // roleMemberCache: add members + + Set roleMembers = roleMemberCache.get(roleName); + roleMembers.addAll(members); + + // memberRoleCache: add members + + for (String member : members) { + if (!memberRoleCache.containsKey(member)) { + memberRoleCache.put(member, new HashSet<>()); + } + final Set rolesForMember = memberRoleCache.get(member); + rolesForMember.add(roleName); + } + } + + void processRoleTrustDomain(String roleName, String trustDomain) { + + if (trustDomain == null) { + return; + } + + if (!trustCache.containsKey(trustDomain)) { + trustCache.put(trustDomain, new HashSet<>()); + } + + final Set rolesForTrustDomain = trustCache.get(trustDomain); + rolesForTrustDomain.add(roleName); + } + + public void processRole(Role role) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Processing role: " + role.getName()); + } + + /* first process members */ + + processRoleMembers(role.getName(), role.getMembers()); + + /* now process trust domains */ + + processRoleTrustDomain(role.getName(), role.getTrust()); + } + + void processAssumeRoleAssertion(Assertion assertion, Map roles) { + + final String roleName = assertion.getRole(); + Role role = roles.get(roleName); + if (role == null) { + return; + } + + /* add the resource as a role name for all the members */ + + processRoleMembers(assertion.getResource(), role.getMembers()); + } + + void processAWSAssumeRoleAssertion(Assertion assertion) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Processing AWS Assume Role for resource: " + assertion.getResource() + + " and role: " + assertion.getRole()); + } + + String role = assertion.getRole(); + if (!awsRoleCache.containsKey(role)) { + awsRoleCache.put(assertion.getRole(), new HashSet<>()); + } + + final Set resourcesForRole = awsRoleCache.get(role); + resourcesForRole.add(assertion.getResource()); + } + + public void processPolicy(String domainName, Policy policy, Map roles) { + + String policyName = policy.getName(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Processing policy: " + policyName); + } + + List assertions = policy.getAssertions(); + if (assertions == null || assertions.isEmpty()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Policy: {} does not have any assertions, skipping." , policyName); + } + return; + } + + for (Assertion assertion : assertions) { + + /* we are only interested in assume_role and + * assume_aws_role assertions */ + + switch (assertion.getAction()) { + case ACTION_ASSUME_AWS_ROLE: + processAWSAssumeRoleAssertion(assertion); + break; + case ACTION_ASSUME_ROLE: + processAssumeRoleAssertion(assertion, roles); + break; + } + } + } + + void processServiceIdentityHosts(String serviceName, List hosts) { + + if (hosts == null || hosts.isEmpty()) { + return; + } + + for (String host : hosts) { + if (!hostCache.containsKey(host)) { + hostCache.put(host, new HashSet<>()); + } + + final Set hostsForService = hostCache.get(host); + hostsForService.add(serviceName); + } + } + + String generateServiceKeyName(String service, String keyId) { + StringBuilder str = new StringBuilder(256); + str.append(service); + str.append("_"); + str.append(keyId); + return str.toString(); + } + + void processServiceIdentityPublicKey(String serviceName, String keyId, String publicKey) { + + if (publicKey == null) { + return; + } + + String keyValue = null; + try { + keyValue = Crypto.ybase64DecodeString(publicKey); + } catch (CryptoException ex) { + LOGGER.error("Invalid public key for " + serviceName + " with id " + keyId + + " with value '" + publicKey + "':" + ex.getMessage()); + } + + if (keyValue != null) { + publicKeyCache.put(generateServiceKeyName(serviceName, keyId), keyValue); + } + } + + void processServiceIdentityPublicKeys(String serviceName, List publicKeys) { + + if (publicKeys == null || publicKeys.isEmpty()) { + return; + } + + for (PublicKeyEntry publicKey : publicKeys) { + processServiceIdentityPublicKey(serviceName, publicKey.getId(), + publicKey.getKey()); + } + } + + public void processServiceIdentity(com.yahoo.athenz.zms.ServiceIdentity service) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Processing service identity: " + service.getName()); + } + + /* first process the hosts for the service */ + + processServiceIdentityHosts(service.getName(), service.getHosts()); + + /* now process the public keys */ + + processServiceIdentityPublicKeys(service.getName(), service.getPublicKeys()); + } + + /** + * Return roles belonging to a member + * @param member whose roles we want + * @return the list of roles + */ + public Set getMemberRoleSet(String member) { + return memberRoleCache.get(member); + } + + /** + * Return the number of members in the cache + */ + public int getMemberCount() { + return memberRoleCache.size(); + } + + /** + * Return the members belonging to a role, inverts {@code getMemberRoleSet} + * @param roleName the role whose members we want + * @return the list of members + */ + public Set getRoleMemberSet(String roleName) { + return roleMemberCache.get(roleName); + } + + public Set getAWSResourceRoleSet(String role) { + return awsRoleCache.get(role); + } + + public Map> getTrustMap() { + return trustCache; + } + + public Map> getHostMap() { + return hostCache; + } + + public Map getPublicKeyMap() { + return publicKeyCache; + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/cache/DataCacheProvider.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/cache/DataCacheProvider.java new file mode 100644 index 00000000000..06b720bd427 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/cache/DataCacheProvider.java @@ -0,0 +1,22 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cache; + +public interface DataCacheProvider { + + public DataCache getDataCache(String domainName); + +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/CertSigner.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/CertSigner.java new file mode 100644 index 00000000000..5556b97cb7a --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/CertSigner.java @@ -0,0 +1,39 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert; + +public interface CertSigner { + + /** + * Generate a signed X509 Certificate based on the given request. The + * certificate should be valid for given number of minutes. The result + * must be the certificate in PEM format + * @param csr Certificate request + * @return X509 Certificate in PEM format + */ + String generateX509Certificate(String csr); + + /** + * Retrieve the CA certificate in PEM format + * @return the CA Certificate + */ + String getCACertificate(); + + /** + * Close the httpClient held by certSigner + */ + void close(); +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/CertSignerFactory.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/CertSignerFactory.java new file mode 100644 index 00000000000..8beebc78d78 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/CertSignerFactory.java @@ -0,0 +1,27 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert; + +import org.eclipse.jetty.client.HttpClient; + +public interface CertSignerFactory { + + /** + * Create and return a new CertSigner instance + * @return CertSigner instance + */ + public CertSigner create(HttpClient httpClient); +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/SvcCertStore.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/SvcCertStore.java new file mode 100644 index 00000000000..0570d2e8877 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/SvcCertStore.java @@ -0,0 +1,38 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert; + +import com.yahoo.athenz.zts.Identity; +import com.yahoo.athenz.zts.InstanceInformation; + +public interface SvcCertStore { + + /** + * Generate Identity for the csr passed. + * @param csr Certificate request + * @param serviceYrn + * @return Identity + */ + Identity generateIdentity(String csr, String serviceYrn); + + /** + * Is the request valid? Is the HostDocument valid and matches the signature + * Does the domain match the one in host document + * @param instanceInformation + * @return boolean true if instanceInformation is valid, false otherwise + */ + boolean isValidRequest(InstanceInformation instanceInformation); +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/SvcCertStoreFactory.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/SvcCertStoreFactory.java new file mode 100644 index 00000000000..7283b4f2c47 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/SvcCertStoreFactory.java @@ -0,0 +1,26 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert; + +public interface SvcCertStoreFactory { + + /** + * Create and return a new SvcCertStore instance + * @param certSigner CertSigner client to use for signing + * @return SvcCertStore instance + */ + public SvcCertStore create(CertSigner certSigner); +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/X509CertSignObject.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/X509CertSignObject.java new file mode 100644 index 00000000000..63b18aa0dbd --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/X509CertSignObject.java @@ -0,0 +1,36 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT) +public class X509CertSignObject { + + private String pem; + + public X509CertSignObject() { + } + + public String getPem() { + return pem; + } + + public X509CertSignObject setPem(String pem) { + this.pem = pem; + return this; + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YCertSigner.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YCertSigner.java new file mode 100644 index 00000000000..277ffa46038 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YCertSigner.java @@ -0,0 +1,158 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert.impl; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import com.yahoo.athenz.zts.ResourceException; +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.cert.X509CertSignObject; +import com.yahoo.athenz.zts.utils.ZTSUtils; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.rdl.JSON; + +public class YCertSigner implements CertSigner { + + private static final Logger LOGGER = LoggerFactory.getLogger(YCertSigner.class); + private HttpClient httpClient = null; + String certUri = null; + + private static final String ZTS_PROP_CERTSIGN_BASE_URI = "athenz.zts.certsign_base_uri"; + private static final String DEFAULT_CERTSIGN_BASE_URI = "https://localhost:443/certsign/v2"; + + public YCertSigner(HttpClient client) { + if (client != null) { + this.httpClient = client; + } else { + // Instantiate and start our HttpClient + httpClient = new HttpClient(ZTSUtils.createSSLContextObject(new String[] {"TLSv1.2"})); + httpClient.setFollowRedirects(false); + try { + httpClient.start(); + } catch (Exception ex) { + LOGGER.error("YCertSigner: unable to start http client", ex); + throw new ResourceException(ResourceException.INTERNAL_SERVER_ERROR, + "Http client not available"); + } + } + + // generate our post and get certificate URIs + + String serverBaseUri = System.getProperty(ZTS_PROP_CERTSIGN_BASE_URI, DEFAULT_CERTSIGN_BASE_URI); + certUri = serverBaseUri + "/x509"; + } + + @Override + public void close() { + try { + if (httpClient != null) { + httpClient.stop(); + } + } catch (Exception ex) { + LOGGER.error("close(): unable to stop httpClient" + ex.getMessage()); + } + } + + @Override + public String generateX509Certificate(String csr) { + + ContentResponse response = null; + try { + Request request = httpClient.POST(certUri); + request.header(HttpHeader.ACCEPT, "application/json"); + request.header(HttpHeader.CONTENT_TYPE, "application/json"); + + X509CertSignObject csrCert = new X509CertSignObject(); + csrCert.setPem(csr); + request.content(new StringContentProvider(JSON.string(csrCert)), "application/json"); + response = request.send(); + + } catch (Exception ex) { + LOGGER.error("generateX509Certificate: unable to fetch requested uri '" + certUri + "': " + + ex.getMessage()); + return null; + } + if (response.getStatus() != HttpStatus.CREATED_201) { + LOGGER.error("generateX509Certificate: unable to fetch requested uri '" + certUri + + "' status: " + response.getStatus()); + return null; + } + + String data = response.getContentAsString(); + if (data == null || data.isEmpty()) { + LOGGER.error("generateX509Certificate: received empty response from uri '" + certUri + + "' status: " + response.getStatus()); + return null; + } + + X509CertSignObject pemCert = null; + try { + pemCert = JSON.fromString(data, X509CertSignObject.class); + } catch (Exception ex) { + LOGGER.error("generateX509Certificate: unable to decode object from '" + certUri + + "' error: " + ex.getMessage()); + } + return (pemCert != null) ? pemCert.getPem() : null; + } + + @Override + public String getCACertificate() { + + ContentResponse response = null; + try { + response = httpClient.GET(certUri); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + LOGGER.error("getCACertificate: unable to fetch requested uri '" + certUri + "': " + + e.getMessage()); + return null; + } + if (response.getStatus() != HttpStatus.OK_200) { + LOGGER.error("getCACertificate: unable to fetch requested uri '" + certUri + + "' status: " + response.getStatus()); + return null; + } + + String data = response.getContentAsString(); + if (data == null || data.isEmpty()) { + LOGGER.error("getCACertificate: received empty response from uri '" + certUri + + "' status: " + response.getStatus()); + return null; + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("getCACertificate: CA Certificate" + data); + } + + X509CertSignObject pemCert = null; + try { + pemCert = JSON.fromString(data, X509CertSignObject.class); + } catch (Exception ex) { + LOGGER.error("getCACertificate: unable to decode object from '" + certUri + + "' error: " + ex.getMessage()); + } + return (pemCert != null) ? pemCert.getPem() : null; + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YCertSignerFactory.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YCertSignerFactory.java new file mode 100644 index 00000000000..65fb6684032 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YCertSignerFactory.java @@ -0,0 +1,29 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert.impl; + +import org.eclipse.jetty.client.HttpClient; + +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.cert.CertSignerFactory; + +public class YCertSignerFactory implements CertSignerFactory { + + @Override + public CertSigner create(HttpClient httpClient) { + return new YCertSigner(httpClient); + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YSvcCertStore.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YSvcCertStore.java new file mode 100644 index 00000000000..7ce452e4c0b --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YSvcCertStore.java @@ -0,0 +1,45 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert.impl; + +import com.yahoo.athenz.zts.*; +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.cert.SvcCertStore; +import com.yahoo.athenz.zts.utils.ZTSUtils; + +public class YSvcCertStore implements SvcCertStore { + CertSigner certSigner = null; + String caPEMCertificate = null; + + public YSvcCertStore() { + + } + + public YSvcCertStore(CertSigner certSigner) { + this.certSigner = certSigner; + if (certSigner != null) { + caPEMCertificate = certSigner.getCACertificate(); + } + } + + public Identity generateIdentity(String csr, String serviceYrn) { + return ZTSUtils.generateIdentity(certSigner, csr, serviceYrn, caPEMCertificate); + } + + public boolean isValidRequest(InstanceInformation instanceInformation) { + return true; + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YSvcCertStoreFactory.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YSvcCertStoreFactory.java new file mode 100644 index 00000000000..5af99f211ca --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/cert/impl/YSvcCertStoreFactory.java @@ -0,0 +1,31 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert.impl; + +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.cert.SvcCertStore; +import com.yahoo.athenz.zts.cert.SvcCertStoreFactory; + +public class YSvcCertStoreFactory implements SvcCertStoreFactory { + + public YSvcCertStoreFactory() { + } + + @Override + public SvcCertStore create(CertSigner certSigner) { + return new YSvcCertStore(certSigner); + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/PrivateKeyStore.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/PrivateKeyStore.java new file mode 100644 index 00000000000..d53a3749488 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/PrivateKeyStore.java @@ -0,0 +1,41 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.pkey; + +import java.security.PrivateKey; + +public interface PrivateKeyStore { + + /** + * Retrieve private key for this ZTS Server instance to sign its tokens + * The private key identifier must be updated in the privateKeyId out + * StringBuilder field. The Private Key Store Factory has the knowledge + * which hostname we're processing this request for. + * @param privateKeyId - out argument - must be updated to include key id + * @return private key for this ZTS Server instance. + */ + default PrivateKey getHostPrivateKey(StringBuilder privateKeyId) { + return null; + } + + default PrivateKey getPrivateKey(String keyName, int keyVersion) { + return null; + } + + default String getPublicKey(String keyName, int keyVersion) { + return null; + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/PrivateKeyStoreFactory.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/PrivateKeyStoreFactory.java new file mode 100644 index 00000000000..f10c9bf03ae --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/PrivateKeyStoreFactory.java @@ -0,0 +1,26 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.pkey; + +public interface PrivateKeyStoreFactory { + + /** + * Create and return a new PrivateKeyStore instance + * @param serverHostName hostname of the ZTS Server instance + * @return PrivateKeyStore instance + */ + public PrivateKeyStore create(String serverHostName); +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStore.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStore.java new file mode 100644 index 00000000000..4d38863b742 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStore.java @@ -0,0 +1,128 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.pkey.file; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.zts.ResourceException; +import com.yahoo.athenz.zts.ZTSConsts; +import com.yahoo.athenz.zts.pkey.PrivateKeyStore; + +public class FilePrivateKeyStore implements PrivateKeyStore { + + private static final Logger LOGGER = LoggerFactory.getLogger(FilePrivateKeyStore.class); + + String rootDir = null; + + public FilePrivateKeyStore() { + + // get the system root directory + + rootDir = System.getenv(ZTSConsts.STR_ENV_ROOT); + if (rootDir == null) { + rootDir = ZTSConsts.STR_DEF_ROOT; + } + } + + @Override + public PrivateKey getHostPrivateKey(StringBuilder privateKeyId) { + + String privKeyName = System.getProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY, + rootDir + "/share/athenz/sys.auth/zts.key"); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("FilePrivateKeyStore: private key file=" + privKeyName); + } + + File privKeyFile = new File(privKeyName); + String key = Crypto.encodedFile(privKeyFile); + PrivateKey pkey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(key)); + + if (pkey == null) { + throw new ResourceException(500, "Unable to retrieve ZTS Server private key"); + } + + privateKeyId.append(System.getProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY_ID, "0")); + return pkey; + } + + @Override + public PrivateKey getPrivateKey(String keyName, int keyVersion) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("FilePrivateKeyStore: private key file=" + keyName); + } + + // if the version is 0 then we're going to take the keyname + // as the filename otherwise we'll append ".v" + // to generated the versioned key filename + + String fileName = keyName; + if (keyVersion != 0) { + fileName += ".v" + keyVersion; + } + File privKeyFile = new File(fileName); + String key = Crypto.encodedFile(privKeyFile); + PrivateKey pkey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(key)); + + if (pkey == null) { + throw new ResourceException(500, "Unable to retrieve private key: " + fileName); + } + + return pkey; + } + + @Override + public String getPublicKey(String keyName, int keyVersion) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("FilePrivateKeyStore: public key file=" + keyName); + } + + // if the version is 0 then we're going to take the keyname + // as the filename otherwise we'll append ".v" + // to generated the versioned key filename + + String fileName = keyName; + if (keyVersion != 0) { + fileName += ".v" + keyVersion; + } + Path path = Paths.get(fileName); + String pkey = null; + try { + pkey = new String(Files.readAllBytes(path)); + } catch (IOException ex) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("FilePrivateKeyStore: unable to read public key", ex); + } + } + + if (pkey == null) { + throw new ResourceException(500, "Unable to retrieve public key: " + fileName); + } + + return pkey; + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStoreFactory.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStoreFactory.java new file mode 100644 index 00000000000..148b69c8498 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStoreFactory.java @@ -0,0 +1,27 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.pkey.file; + +import com.yahoo.athenz.zts.pkey.PrivateKeyStore; +import com.yahoo.athenz.zts.pkey.PrivateKeyStoreFactory; + +public class FilePrivateKeyStoreFactory implements PrivateKeyStoreFactory { + + @Override + public PrivateKeyStore create(String serverHostName) { + return new FilePrivateKeyStore(); + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStore.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStore.java new file mode 100644 index 00000000000..8cc6529b02a --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStore.java @@ -0,0 +1,31 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.pkey.hsm; + +import java.security.PrivateKey; + +import com.yahoo.athenz.zts.pkey.PrivateKeyStore; + +public class HSMPrivateKeyStore implements PrivateKeyStore { + + public HSMPrivateKeyStore() { + } + + @Override + public PrivateKey getHostPrivateKey(StringBuilder privateKeyId) { + return null; + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStoreFactory.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStoreFactory.java new file mode 100644 index 00000000000..7e63e8a01e4 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStoreFactory.java @@ -0,0 +1,27 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.pkey.hsm; + +import com.yahoo.athenz.zts.pkey.PrivateKeyStore; +import com.yahoo.athenz.zts.pkey.PrivateKeyStoreFactory; + +public class HSMPrivateKeyStoreFactory implements PrivateKeyStoreFactory { + + @Override + public PrivateKeyStore create(String serverHostName) { + return new HSMPrivateKeyStore(); + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/store/ChangeLogStore.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/ChangeLogStore.java new file mode 100644 index 00000000000..91eee741daf --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/ChangeLogStore.java @@ -0,0 +1,87 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store; + +import java.util.List; +import java.util.Set; + +import com.yahoo.athenz.zms.SignedDomain; +import com.yahoo.athenz.zms.SignedDomains; + +/** + * An interface that ZTSCore depends on to manage its state. + */ + +public interface ChangeLogStore { + + /** + * Gets the associated domain data for the key + * @param domainName the name of the domain + * @return SignedDomain object of the domain or null if absent + */ + public SignedDomain getSignedDomain(String domainName); + + /** + * Remove the local domain record from the changelog store + * @param domainName the name of the domain + */ + public void removeLocalDomain(String domainName); + + /** + * Save the local domain record from the changelog store + * @param domainName the name of the domain + * @param signedDomain the {@code SignedDomain} for the {@code domainName} supplied + */ + public void saveLocalDomain(String domainName, SignedDomain signedDomain); + + /** + * Returns the names of all domain stored in local repository + * @return List of domain names + */ + public List getLocalDomainList(); + + /** + * Returns the list of all domains configured on server + * @return Set of domain names + */ + public Set getServerDomainList(); + + /** + * Returns the list of domains modified since the last call + * @param lastModTimeBuffer StringBuilder object will be updated to include + * the last modification time for the request. If data store + * successfully updates the local entries in the cache then + * it will call setLastModificationTimestamp with the same value + * @return Array of SignedDomain objects + */ + public SignedDomains getUpdatedSignedDomains(StringBuilder lastModTimeBuffer); + + /** + * Notifies the store to update its changelog last modification + * timestamp. If the value is null then it notifies the stores to + * reset its changelog and during next retrieveDomainUpdates call + * to return set of all domains available in ZMS + * @param lastModTime last modification timestamp + */ + public void setLastModificationTimestamp(String lastModTime); + + /** + * The change log store supports getting a full refresh from + * ZMS Server directly + * @return true if store supports full refresh, false otherwise + */ + public boolean supportsFullRefresh(); +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/store/ChangeLogStoreFactory.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/ChangeLogStoreFactory.java new file mode 100644 index 00000000000..b4d8a7a15af --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/ChangeLogStoreFactory.java @@ -0,0 +1,32 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store; + +import java.security.PrivateKey; + +public interface ChangeLogStoreFactory { + + /** + * Create and return a new ChangeLogStore instance + * @param ztsHomeDir the home directory for the ZTS Server instance (e.g. /home/athenz/var/zts_server) + * @param privateKey the PrivateKey to generate service tokens when communicating with ZMS Server + * @param privateKeyId the private key identifier + * @param cloudStore represents an AWS instance + * @return ChangeLogStore instance + */ + public ChangeLogStore create(String ztsHomeDir, PrivateKey privateKey, + String privateKeyId, CloudStore cloudStore); +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/store/CloudStore.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/CloudStore.java new file mode 100644 index 00000000000..6aa76eb55f8 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/CloudStore.java @@ -0,0 +1,636 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store; + +import java.io.File; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.auth.BasicSessionCredentials; +import com.amazonaws.regions.Region; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; +import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; +import com.amazonaws.services.securitytoken.model.AssumeRoleResult; +import com.amazonaws.services.securitytoken.model.Credentials; +import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest; +import com.amazonaws.services.securitytoken.model.GetCallerIdentityResult; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.auth.util.CryptoException; +import com.yahoo.athenz.zts.AWSInstanceInformation; +import com.yahoo.athenz.zts.AWSTemporaryCredentials; +import com.yahoo.athenz.zts.Identity; +import com.yahoo.athenz.zts.ResourceException; +import com.yahoo.athenz.zts.ZTSConsts; +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.utils.ZTSUtils; +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Struct; +import com.yahoo.rdl.Timestamp; + +public class CloudStore { + + private static final Logger LOGGER = LoggerFactory.getLogger(CloudStore.class); + public static final String ZTS_PROP_AWS_CREDS_UPDATE_TIMEOUT = "athns.zts.aws_creds_update_timeout"; + public static final String ZTS_PROP_AWS_REGION_NAME = "athenz.zts.aws_region_name"; + public static final String ZTS_PROP_AWS_PUBLIC_CERT = "athenz.zts.aws_public_cert"; + + String awsRole = null; + String awsCloud = null; + String awsProfile = null; + String awsAccount = null; + String awsDomain = null; + String awsService = null; + String awsRegion = null; + boolean awsEnabled = false; + CertSigner certSigner = null; + BasicSessionCredentials credentials = null; + Map cloudAccountCache = null; + int credsUpdateTime = 900; + String caPEMCertificate = null; // public key certificate of the certifying authority in PEM format + PublicKey awsPublicKey = null; // AWS public key for validating instance documents + private HttpClient httpClient = null; + + private static ScheduledExecutorService scheduledThreadPool; + + public CloudStore(CertSigner certSigner) { + + // save our cert signer and generate the PEM output of the certificate + + this.certSigner = certSigner; + if (certSigner != null) { + caPEMCertificate = certSigner.getCACertificate(); + } + + // initialize our account cache + + cloudAccountCache = new HashMap(); + + // Instantiate and start our HttpClient + + httpClient = new HttpClient(); + httpClient.setFollowRedirects(false); + try { + httpClient.start(); + } catch (Exception ex) { + LOGGER.error("CloudStore: unable to start http client", ex); + throw new ResourceException(ResourceException.INTERNAL_SERVER_ERROR, + "Http client not available"); + } + + // let's retrieve our AWS public certificate which is posted here: + // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html + + String awsCertFileName = System.getProperty(ZTS_PROP_AWS_PUBLIC_CERT); + if (awsCertFileName != null && !awsCertFileName.isEmpty()) { + File awsCertFile = new File(awsCertFileName); + X509Certificate awsCert = Crypto.loadX509Certificate(awsCertFile); + awsPublicKey = awsCert.getPublicKey(); + } + + // check to see if we are given region name + + awsRegion = System.getProperty(ZTS_PROP_AWS_REGION_NAME); + + // initialize aws support + + awsEnabled = Boolean.parseBoolean(System.getProperty(ZTSConsts.ZTS_PROP_AWS_ENABLED, "false")); + initializeAwsSupport(); + } + + void close() { + if (httpClient != null) { + try { + httpClient.stop(); + } catch (Exception e) { + } + } + } + + public void setHttpClient(HttpClient client) { + if (httpClient != null) { + try { + httpClient.stop(); + } catch (Exception e) { + } + } + httpClient = client; + } + + public boolean isAwsEnabled() { + return awsEnabled; + } + + void initializeAwsSupport() { + + // these operations require initialization of aws objects so + // we'll process them only if we have been configured to run in aws + + if (!awsEnabled) { + return; + } + + // initialize and load our bootstrap data + + if (!loadBootMetaData()) { + throw new ResourceException(ResourceException.INTERNAL_SERVER_ERROR, + "Unable to load boot data"); + } + + // finally fetch the role credentials + + if (!fetchRoleCredentials()) { + throw new ResourceException(ResourceException.INTERNAL_SERVER_ERROR, + "Unable to fetch aws role credentials"); + } + + // Start our thread to get aws temporary credentials + + credsUpdateTime = ZTSUtils.retrieveConfigSetting(ZTS_PROP_AWS_CREDS_UPDATE_TIMEOUT, 900); + + scheduledThreadPool = Executors.newScheduledThreadPool(1); + scheduledThreadPool.scheduleAtFixedRate(new RoleCredentialsFetcher(), credsUpdateTime, + credsUpdateTime, TimeUnit.SECONDS); + } + + public AmazonS3 getS3Client() { + + if (!awsEnabled) { + throw new ResourceException(ResourceException.INTERNAL_SERVER_ERROR, + "AWS Support not enabled"); + } + + if (credentials == null) { + throw new ResourceException(ResourceException.INTERNAL_SERVER_ERROR, + "AWS Role credentials are not available"); + } + + AmazonS3Client s3 = new AmazonS3Client(credentials); + if (awsRegion != null) { + s3.setRegion(Region.getRegion(Regions.fromName(awsRegion))); + } + return s3; + } + + boolean loadBootMetaData() { + + // first load the dynamic document + + String document = getMetaData("/dynamic/instance-identity/document"); + if (document == null) { + return false; + } + + try { + if (!parseInstanceInfo(document)) { + return false; + } + } catch (Exception ex) { + LOGGER.error("CloudStore: unable to parse instance identity document: " + document, ex); + return false; + } + + // then the document signature + + String docSignature = getMetaData("/dynamic/instance-identity/pkcs7"); + if (docSignature == null) { + return false; + } + + // next the iam profile data + + String iamRole = getMetaData("/meta-data/iam/info"); + if (iamRole == null) { + return false; + } + + // now parse and extract the profile details. we'll catch + // all possible index out of bounds exceptions here and just + // report the error and return false + + try { + if (!parseIamRoleInfo(iamRole)) { + return false; + } + } catch (Exception ex) { + LOGGER.error("CloudStore: unable to parse iam role data: " + iamRole, ex); + return false; + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CloudStore: service meta information:"); + LOGGER.debug("CloudStore: cloud: " + awsCloud); + LOGGER.debug("CloudStore: role: " + awsRole); + LOGGER.debug("CloudStore: profile: " + awsProfile); + LOGGER.debug("CloudStore: account: " + awsAccount); + LOGGER.debug("CloudStore: domain: " + awsDomain); + LOGGER.debug("CloudStore: service: " + awsService); + LOGGER.debug("CloudStore: region: " + awsRegion); + } + return true; + } + + boolean parseInstanceInfo(String document) { + + Struct instStruct = null; + try { + instStruct = JSON.fromString(document, Struct.class); + } catch (Exception ex) { + LOGGER.error("CloudStore: unable to parse instance identity document", ex); + } + + if (instStruct == null) { + LOGGER.error("CloudStore: unable to parse instance identity document: " + document); + return false; + } + + // if we don't extract our account id here, we'll try + // to retrieve it from our iam role name + + awsAccount = instStruct.getString("accountId"); + + // if we're overriding the region name, then we'll + // extract that value here + + if (awsRegion == null || awsRegion.isEmpty()) { + awsRegion = instStruct.getString("region"); + if (awsRegion == null || awsRegion.isEmpty()) { + LOGGER.error("CloudStore: unable to extract region from instance identity document: " + document); + return false; + } + } + + return true; + } + + boolean parseIamRoleInfo(String iamRole) { + + Struct iamRoleStruct = null; + try { + iamRoleStruct = JSON.fromString(iamRole, Struct.class); + } catch (Exception ex) { + LOGGER.error("CloudStore: unable to parse iam role data", ex); + } + + if (iamRoleStruct == null) { + LOGGER.error("CloudStore: unable to parse iam role data: " + iamRole); + return false; + } + + // extract and parse our profile arn + // "InstanceProfileArn" : "arn:aws:iam::1111111111111:instance-profile/iaas.athenz.zts,athenz", + + String profileArn = iamRoleStruct.getString("InstanceProfileArn"); + if (profileArn == null || profileArn.isEmpty()) { + LOGGER.error("CloudStore: unable to extract InstanceProfileArn from iam role data: " + iamRole); + return false; + } + + if (!parseInstanceProfileArn(profileArn)) { + return false; + } + + return true; + } + + boolean parseInstanceProfileArn(String profileArn) { + + // "InstanceProfileArn" : "arn:aws:iam::1111111111111:instance-profile/iaas.athenz.zts,athenz", + + if (!profileArn.startsWith("arn:aws:iam::")) { + LOGGER.error("CloudStore: InstanceProfileArn does not start with 'arn:aws:iam::' : " + profileArn); + return false; + } + + int idx = profileArn.indexOf(":instance-profile/"); + if (idx == -1) { + LOGGER.error("CloudStore: unable to parse InstanceProfileArn: " + profileArn); + return false; + } + + awsProfile = profileArn.substring(idx + ":instance-profile/".length()); + + // we should already have our aws account data but in case we didn't + // get it from our instance document, we'll extract it from the profile name + + if (awsAccount == null || awsAccount.isEmpty()) { + awsAccount = profileArn.substring("arn:aws:iam::".length(), idx); + } + + // make sure we have valid profile and account data + + if (awsProfile.isEmpty() || awsAccount.isEmpty()) { + LOGGER.error("CloudStore: unable to extract profile/account data from InstanceProfileArn: " + profileArn); + return false; + } + + // we need to extract the role and cloud names from the profile + + String[] comps = awsProfile.split(","); + if (comps.length != 2) { + LOGGER.error("CloudStore: unable to extract role/cloud name from profile: " + awsProfile); + return false; + } + + awsRole = comps[0]; + awsCloud = comps[1]; + + // retrieve our domain and service names from our role name + + idx = awsRole.lastIndexOf('.'); + if (idx == -1) { + LOGGER.error("CloudStore: malformed service name: " + awsRole); + return false; + } + + awsDomain = awsRole.substring(0, idx); + awsService = awsRole.substring(idx + 1); + + // make sure we have valid service and domain values + + if (awsDomain.isEmpty() || awsService.isEmpty()) { + LOGGER.error("CloudStore: unable to extract domain/service data from profile: " + awsRole); + return false; + } + + return true; + } + + boolean fetchRoleCredentials() { + + // verify that we have a valid awsRole already retrieved + + if (awsRole == null || awsRole.isEmpty()) { + LOGGER.error("CloudStore: awsRole is not avaialble to fetch role credentials"); + return false; + } + + String creds = getMetaData("/meta-data/iam/security-credentials/" + awsRole); + if (creds == null) { + return false; + } + + Struct credsStruct = null; + try { + credsStruct = JSON.fromString(creds, Struct.class); + } catch (Exception ex) { + LOGGER.error("CloudStore: unable to parse role credentials data", ex); + } + + if (credsStruct == null) { + LOGGER.error("CloudStore: unable to parse role credentials data: " + creds); + return false; + } + + String accessKeyId = credsStruct.getString("AccessKeyId"); + String secretAccessKey = credsStruct.getString("SecretAccessKey"); + String token = credsStruct.getString("Token"); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CloudStore: access key id: " + accessKeyId); + LOGGER.debug("CloudStore: secret access key: " + secretAccessKey); + } + + try { + credentials = new BasicSessionCredentials(accessKeyId, secretAccessKey, token); + } catch (Exception ex) { + LOGGER.error("CloudStore: unable to generate session credentials from: " + creds, ex); + return false; + } + + return true; + } + + String getMetaData(String path) { + + final String baseUri = "http://169.254.169.254/latest"; + ContentResponse response = null; + try { + response = httpClient.GET(baseUri + path); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + LOGGER.error("CloudStore: unable to fetch requested uri '" + path + "': " + + e.getMessage()); + return null; + } + if (response.getStatus() != 200) { + LOGGER.error("CloudStore: unable to fetch requested uri '" + path + + "' status: " + response.getStatus()); + return null; + } + + String data = response.getContentAsString(); + if (data == null || data.isEmpty()) { + LOGGER.error("CloudStore: received empty response from uri '" + path + + "' status: " + response.getStatus()); + return null; + } + + return data; + } + + AssumeRoleRequest getAssumeRoleRequest(String account, String roleName, String principal) { + + // assume the target role to get the credentials for the client + // aws format is arn:aws:iam:::role/ + + String arn = "arn:aws:iam::" + account + ":role/" + roleName; + + AssumeRoleRequest req = new AssumeRoleRequest(); + req.setRoleArn(arn); + req.setRoleSessionName(principal); + + return req; + } + + AWSSecurityTokenServiceClient getTokenServiceClient() { + return new AWSSecurityTokenServiceClient(credentials); + } + + public AWSTemporaryCredentials assumeAWSRole(String account, String roleName, String principal) { + + if (!awsEnabled) { + throw new ResourceException(ResourceException.INTERNAL_SERVER_ERROR, + "AWS Support not enabled"); + } + + AssumeRoleRequest req = getAssumeRoleRequest(account, roleName, principal); + + AWSTemporaryCredentials tempCreds = null; + try { + AWSSecurityTokenServiceClient client = getTokenServiceClient(); + AssumeRoleResult res = client.assumeRole(req); + + Credentials awsCreds = res.getCredentials(); + tempCreds = new AWSTemporaryCredentials() + .setAccessKeyId(awsCreds.getAccessKeyId()) + .setSecretAccessKey(awsCreds.getSecretAccessKey()) + .setSessionToken(awsCreds.getSessionToken()) + .setExpiration(Timestamp.fromMillis(awsCreds.getExpiration().getTime())); + + } catch (Exception ex) { + LOGGER.error("CloudStore: assumeAWSRole - unable to assume role: " + ex.getMessage()); + return null; + } + + return tempCreds; + } + + public String getAWSAccount(String domainName) { + return cloudAccountCache.get(domainName); + } + + void updateAccount(String domainName, String account) { + + /* if we have a value specified for the domain, then we're just + * going to insert it into our map and update the record. If + * the new value is not present and we had a value stored before + * then let's remove it */ + + if (account != null && !account.isEmpty()) { + cloudAccountCache.put(domainName, account); + } else if (cloudAccountCache.get(domainName) != null) { + cloudAccountCache.remove(domainName); + } + } + + public boolean validateInstanceDocument(String document, String signature) { + + if (document == null || document.isEmpty()) { + LOGGER.error("validateInstanceDocument: AWS instance document is empty"); + return false; + } + + if (signature == null || signature.isEmpty()) { + LOGGER.error("validateInstanceDocument: AWS instance document signature is empty"); + return false; + } + + if (awsPublicKey == null) { + LOGGER.error("validateInstanceDocument: AWS Public key is not available"); + return false; + } + + boolean valid = false; + try { + valid = Crypto.validatePKCS7Signature(document, signature, awsPublicKey); + } catch (CryptoException ex) { + LOGGER.error("validateInstanceDocument: unable to validate AWS instance document", ex); + } + return valid; + } + + class RoleCredentialsFetcher implements Runnable { + + @Override + public void run() { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("RoleCredentialsFetcher: Starting aws role credentials fetcher task..."); + } + + try { + fetchRoleCredentials(); + } catch (Exception ex) { + LOGGER.error("RoleCredentialsFetcher: unable to fetch aws role credentials", ex); + } + } + } + + AWSSecurityTokenServiceClient getInstanceClient(AWSInstanceInformation info) { + + String access = info.getAccess(); + if (access == null || access.isEmpty()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("getInstanceClient: No access key id available in instance document"); + } + return null; + } + + String secret = info.getSecret(); + if (secret == null || secret.isEmpty()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("getInstanceClient: No secret access key available in instance document"); + } + return null; + } + + String token = info.getToken(); + if (token == null || token.isEmpty()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("getInstanceClient: No token available in instance document"); + } + return null; + } + + BasicSessionCredentials creds = new BasicSessionCredentials(access, secret, token); + return new AWSSecurityTokenServiceClient(creds); + } + + public boolean verifyInstanceIdentity(AWSInstanceInformation info) { + + StringBuilder serviceBuilder = new StringBuilder(256); + serviceBuilder.append(info.getDomain()).append('.').append(info.getService()); + String service = serviceBuilder.toString(); + + GetCallerIdentityRequest req = new GetCallerIdentityRequest(); + + try { + AWSSecurityTokenServiceClient client = getInstanceClient(info); + if (client == null) { + LOGGER.error("CloudStore: verifyInstanceIdentity - unable to get AWS STS client object"); + return false; + } + + GetCallerIdentityResult res = client.getCallerIdentity(req); + if (res == null) { + LOGGER.error("CloudStore: verifyInstanceIdentity - unable to get caller identity"); + return false; + } + + String arn = "arn:aws:sts::" + info.getAccount() + ":assumed-role/" + service + "/"; + if (!res.getArn().startsWith(arn)) { + LOGGER.error("CloudStore: verifyInstanceIdentity - ARN mismatch - request:" + + arn + " caller-identity: " + res.getArn()); + return false; + } + + return true; + + } catch (Exception ex) { + LOGGER.error("CloudStore: verifyInstanceIdentity - unable get caller identity: " + ex.getMessage()); + return false; + } + } + + public Identity generateIdentity(String csr, String serviceYrn) { + return ZTSUtils.generateIdentity(certSigner, csr, serviceYrn, caPEMCertificate); + } +} + diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/store/DataStore.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/DataStore.java new file mode 100644 index 00000000000..095bceb899f --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/DataStore.java @@ -0,0 +1,925 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.yahoo.rdl.*; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.config.AthenzConfig; +import com.yahoo.athenz.common.server.util.StringUtils; +import com.yahoo.athenz.common.utils.SignUtils; +import com.yahoo.athenz.zms.DomainData; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.SignedDomain; +import com.yahoo.athenz.zms.SignedDomains; +import com.yahoo.athenz.zts.HostServices; +import com.yahoo.athenz.zts.ZTSConsts; +import com.yahoo.athenz.zts.cache.DataCache; +import com.yahoo.athenz.zts.cache.DataCacheProvider; +import com.yahoo.athenz.zts.utils.ZTSUtils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DataStore implements DataCacheProvider { + + ChangeLogStore changeLogStore = null; + private CloudStore cloudStore = null; + private final Cache cacheStore; + final Cache zmsPublicKeyCache; + final Map> hostCache; + final Map publicKeyCache; + + long updDomainRefreshTime = 60; + long delDomainRefreshTime = 3600; + long lastDeleteRunTime = 0; + + private static ScheduledExecutorService scheduledThreadPool; + + private static final String ROLE_POSTFIX = ":role."; + + private final ReentrantReadWriteLock hostRWLock = new ReentrantReadWriteLock(); + private final Lock hostRLock = hostRWLock.readLock(); + private final Lock hostWLock = hostRWLock.writeLock(); + + private final ReentrantReadWriteLock pkeyRWLock = new ReentrantReadWriteLock(); + private final Lock pkeyRLock = pkeyRWLock.readLock(); + private final Lock pkeyWLock = pkeyRWLock.writeLock(); + + private static final String ZTS_PROP_DOMAIN_UPDATE_TIMEOUT = "athenz.zts.zms_domain_update_timeout"; + private static final String ZTS_PROP_DOMAIN_DELETE_TIMEOUT = "athenz.zts.zms_domain_delete_timeout"; + + private static final Logger LOGGER = LoggerFactory.getLogger(DataStore.class); + + public DataStore(ChangeLogStore clogStore, CloudStore cloudStore) { + + /* save our store objects */ + + this.changeLogStore = clogStore; + this.setCloudStore(cloudStore); + + /* generate our cache stores */ + + cacheStore = CacheBuilder.newBuilder().concurrencyLevel(25).build(); + zmsPublicKeyCache = CacheBuilder.newBuilder().concurrencyLevel(25).build(); + + hostCache = new HashMap<>(); + publicKeyCache = new HashMap<>(); + + /* our configured values are going to be in seconds so we need + * to convert our input in seconds to milliseconds */ + + updDomainRefreshTime = ZTSUtils.retrieveConfigSetting(ZTS_PROP_DOMAIN_UPDATE_TIMEOUT, 60); + delDomainRefreshTime = ZTSUtils.retrieveConfigSetting(ZTS_PROP_DOMAIN_DELETE_TIMEOUT, 3600); + + /* we will not let our domain delete update time be shorter + * than the domain update time so if tha't the case we'll + * set both to be the same value */ + + if (delDomainRefreshTime < updDomainRefreshTime) { + delDomainRefreshTime = updDomainRefreshTime; + } + + lastDeleteRunTime = System.currentTimeMillis(); + + /* load the zms public key from configuration files */ + + loadZMSPublicKeys(); + } + + String generateServiceKeyName(String domain, String service, String keyId) { + StringBuilder str = new StringBuilder(256); + str.append(domain); + str.append("."); + str.append(service); + str.append("_"); + str.append(keyId); + return str.toString(); + } + + void loadZMSPublicKeys() { + + String rootDir = System.getenv(ZTSConsts.STR_ENV_ROOT); + if (rootDir == null) { + rootDir = ZTSConsts.STR_DEF_ROOT; + } + + String confFileName = System.getProperty(ZTSConsts.ZTS_PROP_ATHENZ_CONF, + rootDir + "/conf/athenz/athenz.conf"); + Path path = Paths.get(confFileName); + AthenzConfig conf = null; + try { + conf = JSON.fromBytes(Files.readAllBytes(path), AthenzConfig.class); + ArrayList publicKeys = conf.getZmsPublicKeys(); + if (publicKeys != null) { + for (com.yahoo.athenz.zms.PublicKeyEntry publicKey : publicKeys) { + String id = publicKey.getId(); + String key = publicKey.getKey(); + if (key == null || id == null) { + continue; + } + PublicKey zmsKey = Crypto.loadPublicKey(Crypto.ybase64DecodeString(key)); + zmsPublicKeyCache.put(id, zmsKey); + } + } + } catch (IOException e) { + LOGGER.info("Unable to parse conf file " + confFileName); + return; + } + } + + boolean processLocalDomains(List localDomainList) { + + /* we can't have a lastModTime set if we have no local + * domains - in this case we're going to reset */ + + if (localDomainList.isEmpty()) { + return false; + } + + /* first we need to retrieve the list of domains from ZMS so we + * know what domains have been deleted already (if any) */ + + Set zmsDomainList = changeLogStore.getServerDomainList(); + if (zmsDomainList == null) { + return false; + } + + for (String domainName : localDomainList) { + + /* make sure this domain is still active in ZMS otherwise + * we'll just remove our local copy */ + + if (!zmsDomainList.contains(domainName)) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Removing local domain: " + domainName + ". Domain not in ZMS anymore."); + } + + deleteDomain(domainName); + continue; + } + + /* if we get a failure when processing a local domain then it + * indicates that we had an invalid domain file (possibly + * corrupted or hacked. In this case we're going to drop + * everything and request a full refresh from ZMS only if the + * change log store supports that functionality. Otherwise, + * we're going to just skip the domain and continue. */ + + if (!processLocalDomain(domainName) && changeLogStore.supportsFullRefresh()) { + return false; + } + } + + return true; + } + + public boolean init() { + + /* now let's retrieve the list of locally saved domains */ + + List localDomainList = changeLogStore.getLocalDomainList(); + + /* if we are not able to successfully process our local domains + * then we're going to ask our store to reset the changes + * and give us the list of all domains from ZMS */ + + if (!processLocalDomains(localDomainList)) { + + changeLogStore.setLastModificationTimestamp(null); + + /* if we have decided that we need to a full refresh + * we need to clean up and remove any cached domains */ + + for (String domainName : localDomainList) { + deleteDomain(domainName); + } + } + + /* after our local files have been processed now we need to + * retrieve the domains that were modified since the last + * modification time */ + + if (!processDomainUpdates()) { + return false; + } + + /* Start our monitoring thread to get changes from ZMS */ + + scheduledThreadPool = Executors.newScheduledThreadPool(1); + scheduledThreadPool.scheduleAtFixedRate(new DataUpdater(), updDomainRefreshTime, + updDomainRefreshTime, TimeUnit.SECONDS); + + return true; + } + + boolean processLocalDomain(String domainName) { + + boolean result = false; + try { + result = processDomain(changeLogStore.getSignedDomain(domainName), false); + } catch (Exception ex) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Unable to process local domain " + domainName + ": " + ex.getMessage()); + } + } + + if (!result) { + LOGGER.error("Invalid local domain: " + domainName + ". Full refresh from ZMS required."); + } + + return result; + } + + boolean validateSignedDomain(SignedDomain signedDomain) { + + DomainData domainData = signedDomain.getDomain(); + String keyId = signedDomain.getKeyId(); + String signature = signedDomain.getSignature(); + + PublicKey zmsKey = zmsPublicKeyCache.getIfPresent(keyId == null ? "0" : keyId); + if (zmsKey == null) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("ZMS Public Key (id: " + keyId + ") not available"); + } + + return false; + } + + boolean result = Crypto.verify(SignUtils.asCanonicalString(domainData), zmsKey, signature); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Domain '" + domainData.getName() + "' signature validation: " + result); + } + + return result; + } + + void processDomainRoles(DomainData domainData, DataCache domainCache) { + + List roles = domainData.getRoles(); + if (roles == null) { + return; + } + + for (Role role : roles) { + domainCache.processRole(role); + } + } + + void processDomainPolicies(DomainData domainData, DataCache domainCache) { + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = domainData.getPolicies(); + if (signedPolicies == null) { + return; + } + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = signedPolicies.getContents(); + if (domainPolicies == null) { + return; + } + + List policies = domainPolicies.getPolicies(); + if (policies == null) { + return; + } + + List roles = domainData.getRoles(); + HashMap roleMap = new HashMap<>(); + for (Role role : roles) { + roleMap.put(role.getName(), role); + } + for (com.yahoo.athenz.zms.Policy policy : policies) { + domainCache.processPolicy(domainData.getName(), policy, roleMap); + } + } + + void processDomainServiceIdentities(DomainData domainData, DataCache domainCache) { + + List services = domainData.getServices(); + if (services == null) { + return; + } + + for (com.yahoo.athenz.zms.ServiceIdentity service : services) { + domainCache.processServiceIdentity(service); + } + } + + public boolean processDomain(SignedDomain signedDomain, boolean saveInStore) { + + DomainData domainData = signedDomain.getDomain(); + String domainName = domainData.getName(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Processing domain: " + domainName); + } + + /* before doing anything else let's validate our domain */ + + if (!validateSignedDomain(signedDomain)) { + return false; + } + + /* generate our cache object */ + + DataCache domainCache = new DataCache(); + + /* process the roles for this domain */ + + processDomainRoles(domainData, domainCache); + + /* process the policies for this domain */ + + processDomainPolicies(domainData, domainCache); + + /* finally process the service identities */ + + processDomainServiceIdentities(domainData, domainCache); + + /* save the full domain object with the cache entry itself + * since we need to that information to handle + * getServiceIdentity and getServiceIdentityList requests */ + + domainCache.setDomainData(domainData); + + /* add the entry to the cache and struct store */ + + addDomainToCache(domainName, domainCache); + + if (saveInStore) { + changeLogStore.saveLocalDomain(domainName, signedDomain); + } + + return true; + } + + boolean validDomainListResponse(Set zmsDomainList) { + + /* we're doing some basic validation to make sure our + * retrieved zms domain list is correct. At minimum our + * list must not be empty and include our sys.auth + * domain */ + + if (zmsDomainList.isEmpty()) { + return false; + } + + if (!zmsDomainList.contains(ZTSConsts.ATHENZ_SYS_DOMAIN)) { + return false; + } + + return true; + } + + // API + public boolean processDomainDeletes() { + + /* first let's retrieve the list domains loaded into + * our local cache */ + + ArrayList localDomainList = new ArrayList<>(getCacheStore().asMap().keySet()); + if (localDomainList.isEmpty()) { + return true; + } + + /* now retrieve the list of domains from ZMS */ + + Set zmsDomainList = changeLogStore.getServerDomainList(); + if (zmsDomainList == null) { + return false; + } + + /* make sure we don't have an empty list response + * from ZMS that would cause all of our domains + * to be deleted */ + + if (!validDomainListResponse(zmsDomainList)) { + return false; + } + + /* go through each local domain and if it doesn't + * exist in the list returned from ZMS we're going to + * delete that domain from our cache and change log store */ + + for (String domainName : localDomainList) { + + if (!zmsDomainList.contains(domainName)) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Removing local domain: " + domainName + ". Domain not in ZMS anymore."); + } + deleteDomain(domainName); + } + } + + return true; + } + + // Internal + void deleteDomain(String domainName) { + + /* first delete our data from the cache */ + + deleteDomainFromCache(domainName); + + /* then delete it from the struct store */ + + changeLogStore.removeLocalDomain(domainName); + } + + // Internal + boolean processSignedDomains(SignedDomains signedDomains) { + + /* if we have received no data from ZMS server then we're not + * going to update our last modification time and instead we'll + * just continue using the old one until we get some updates + * from ZMS Server */ + + if (signedDomains == null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("No updates received from ZMS Server"); + } + return true; + } + + /* now process all of our domains */ + + List domains = signedDomains.getDomains(); + if (domains == null || domains.isEmpty()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("No updates received from ZMS Server"); + } + return true; + } + + for (SignedDomain domain : domains) { + if (!processDomain(domain, true)) { + return false; + } + } + + return true; + } + + /** + * Poll for new domains and updated domains from the ChangeLogStore (ZMS). + * Called by {@code DataUpdater.run()} thread. Deletes are handled separately in {@code processDomainDeletes()} + * @return true if we have updates, false otherwise + */ + public boolean processDomainUpdates() { + + StringBuilder lastModTimestamp = new StringBuilder(128); + SignedDomains signedDomains = changeLogStore.getUpdatedSignedDomains(lastModTimestamp); + + /* if our data back was null and the last mod timestamp + * is also empty then we had a failure */ + + if (signedDomains == null && lastModTimestamp.length() == 0) { + return false; + } + + /* process all of our received updated domains */ + + boolean result = processSignedDomains(signedDomains); + if (result) { + changeLogStore.setLastModificationTimestamp(lastModTimestamp.toString()); + } + + return result; + } + + // API + public DomainData getDomainData(String name) { + + DataCache data = getCacheStore().getIfPresent(name); + if (data == null) { + return null; + } + return data.getDomainData(); + } + + // Internal + void addHostEntries(Map> hostMap) { + + if (hostMap == null || hostMap.isEmpty()) { + return; + } + + for (Map.Entry> entry : hostMap.entrySet()) { + List services = hostCache.get(entry.getKey()); + if (services == null) { + services = new ArrayList(); + hostCache.put(entry.getKey(), services); + } + services.addAll(entry.getValue()); + } + } + + // Internal + void removeHostEntries(Map> hostMap) { + + if (hostMap == null || hostMap.isEmpty()) { + return; + } + + for (Map.Entry> entry : hostMap.entrySet()) { + List services = hostCache.get(entry.getKey()); + if (services != null) { + services.removeAll(entry.getValue()); + } + } + } + + // Internal + void addPublicKeys(Map publicKeyMap) { + + if (publicKeyMap == null || publicKeyMap.isEmpty()) { + return; + } + + for (Map.Entry entry : publicKeyMap.entrySet()) { + publicKeyCache.put(entry.getKey(), entry.getValue()); + } + } + + // Internal + void removePublicKeys(Map publicKeyMap) { + + if (publicKeyMap == null || publicKeyMap.isEmpty()) { + return; + } + + for (Map.Entry entry : publicKeyMap.entrySet()) { + publicKeyCache.remove(entry.getKey()); + } + } + + // Internal + public void addDomainToCache(String name, DataCache dataCache) { + + /* before update the cache store with our updated data + * we need to remove the old data host and public key sets */ + + DataCache oldDataCache = getCacheStore().getIfPresent(name); + + try { + hostWLock.lock(); + if (oldDataCache != null) { + removeHostEntries(oldDataCache.getHostMap()); + } + addHostEntries(dataCache.getHostMap()); + } finally { + hostWLock.unlock(); + } + + try { + pkeyWLock.lock(); + if (oldDataCache != null) { + removePublicKeys(oldDataCache.getPublicKeyMap()); + } + addPublicKeys(dataCache.getPublicKeyMap()); + } finally { + pkeyWLock.unlock(); + } + + /* now let's see if we have a cloud account defined + * and update accordingly */ + + if (getCloudStore() != null) { + getCloudStore().updateAccount(name, dataCache.getDomainData().getAccount()); + } + + /* update the cache for the given domain */ + + getCacheStore().put(name, dataCache); + } + + // Internal + void deleteDomainFromCache(String name) { + + /* before we delete the domain from our cache, we need to + * remove the old data host and public key sets */ + + DataCache data = getCacheStore().getIfPresent(name); + if (data == null) { + return; + } + + try { + hostWLock.lock(); + removeHostEntries(data.getHostMap()); + } finally { + hostWLock.unlock(); + } + + try { + pkeyWLock.lock(); + removePublicKeys(data.getPublicKeyMap()); + } finally { + pkeyWLock.unlock(); + } + + getCacheStore().invalidate(name); + } + + // Internal + String roleCheckValue(String role, String prefix) { + + if (role == null) { + return null; + } + + String roleCheck = null; + if (!role.startsWith(prefix)) { + roleCheck = prefix + role; + } else { + roleCheck = role; + } + + return roleCheck; + } + + // Internal + void processStandardMembership(Set memberRoles, String rolePrefix, + String roleName, List accessibleRoles, boolean keepFullName) { + + /* if we have no member roles, then we haven't added anything + * to our return result list */ + + if (memberRoles == null) { + return; + } + + for (String member : memberRoles) { + addRoleToList(member, rolePrefix, roleName, accessibleRoles, keepFullName); + } + } + + // Internal + void processTrustMembership(DataCache data, String identity, String rolePrefix, + String roleName, List accessibleRoles, boolean keepFullName) { + + Map> trustedRolesMap = data.getTrustMap(); + + /* iterate through all trusted domains */ + + for (Map.Entry> trustedRole : trustedRolesMap.entrySet()) { + + processTrustedDomain(getCacheStore().getIfPresent(trustedRole.getKey()), + identity, rolePrefix, roleName, trustedRole.getValue(), + accessibleRoles, keepFullName); + } + } + + // API + @Override + public DataCache getDataCache(String domainName) { + return getCacheStore().getIfPresent(domainName); + } + + // API + public void getAccessibleRoles(DataCache data, String domainName, String identity, + String roleName, List accessibleRoles, boolean keepFullName) { + + /* if the domain hasn't been processed then we don't have anything to do */ + + if (data == null) { + return; + } + + String rolePrefix = domainName + ROLE_POSTFIX; + + /* first look through the members to see if the given identity is + * included in the list explicitly */ + + processStandardMembership(data.getMemberRoleSet(identity), + rolePrefix, roleName, accessibleRoles, keepFullName); + + /* now process all the roles that have trusted domain specified */ + + processTrustMembership(data, identity, rolePrefix, roleName, + accessibleRoles, keepFullName); + } + + // Internal + boolean checkRoleSet(String role, Set checkSet) { + + if (checkSet == null) { + return true; + } + + return checkSet.contains(role); + } + + // Internal + void addRoleToList(String role, String rolePrefix, String roleName, + List accessibleRoles, boolean keepFullName) { + + /* any roles we return must start with the domain role prefix */ + + if (!role.startsWith(rolePrefix)) { + return; + } + + /* and it must end with the suffix if specified */ + + if (roleName != null && !role.endsWith(roleName)) { + return; + } + + /* when returning the value we're going to skip the prefix */ + + if (keepFullName) { + accessibleRoles.add(role); + } else { + accessibleRoles.add(role.substring(rolePrefix.length())); + } + } + + // Internal + boolean roleMatchInSet(String role, Set memberRoles) { + + /* since most of the roles will not have wildcards we're + * going to carry out a simple contains check here and if + * that's successful then we're done and we don't have to + * do possibly more expensive regex checks */ + + if (memberRoles.contains(role)) { + return true; + } + + /* no match so let's try the regex pattern check */ + + String rolePattern = null; + for (String memberRole : memberRoles) { + rolePattern = StringUtils.patternFromGlob(memberRole); + if (role.matches(rolePattern)) { + return true; + } + } + + return false; + } + + // Internal + void processSingleTrustedDomainRole(String roleName, String rolePrefix, String roleSuffix, + Set memberRoles, List accessibleRoles, boolean keepFullName) { + + /* since our member role set can include wildcard domains we + * need to match the role as oppose to a direct check if the + * set contains the name */ + + if (!roleMatchInSet(roleName, memberRoles)) { + return; + } + + /* now check if the role is in the resource list as well */ + + addRoleToList(roleName, rolePrefix, roleSuffix, accessibleRoles, keepFullName); + } + + // Internal + void processTrustedDomain(DataCache trustData, String identity, String rolePrefix, + String roleSuffix, Set trustedResources, List accessibleRoles, + boolean keepFullName) { + + /* verify that our data cache and list of trusted resources are valid */ + + if (trustData == null || trustedResources == null) { + return; + } + + /* if we have no member roles, then return right away */ + + Set memberRoles = trustData.getMemberRoleSet(identity); + if (memberRoles == null) { + return; + } + + for (String resource : trustedResources) { + + /* in this case our resource is the role name */ + + processSingleTrustedDomainRole(resource, rolePrefix, roleSuffix, + memberRoles, accessibleRoles, keepFullName); + } + } + + // API + public String getPublicKey(String domain, String service, String keyId) { + + String publicKeyName = generateServiceKeyName(domain, service, keyId); + String publicKey = null; + + try { + pkeyRLock.lock(); + publicKey = publicKeyCache.get(publicKeyName); + } finally { + pkeyRLock.unlock(); + } + + if (publicKey == null && LOGGER.isDebugEnabled()) { + LOGGER.debug("Public key: " + publicKeyName + " not available"); + } + + return publicKey; + } + + // API + public HostServices getHostServices(String host) { + + HostServices result = new HostServices().setHost(host); + + try { + hostRLock.lock(); + + /* we need to make a copy of our list as oppose to just returning + * a reference since once we release the host read lock that list + * can be modified by the updater thread */ + + List services = hostCache.get(host); + if (services != null) { + result.setNames(new ArrayList<>(services)); + } + } finally { + hostRLock.unlock(); + } + + return result; + } + + public CloudStore getCloudStore() { + return cloudStore; + } + + public void setCloudStore(CloudStore cloudStore) { + this.cloudStore = cloudStore; + } + + public Cache getCacheStore() { + return cacheStore; + } + + class DataUpdater implements Runnable { + + @Override + public void run() { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("DataUpdater: Starting data updater thread..."); + } + + try { + processDomainUpdates(); + + /* check to see if we need to handle our delete domain list - + * make sure refresh time is converted to millis */ + + if (System.currentTimeMillis() - lastDeleteRunTime > delDomainRefreshTime * 1000) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("DataUpdater: Processing domain delete checks..."); + } + + processDomainDeletes(); + lastDeleteRunTime = System.currentTimeMillis(); + } + + } catch (Exception ex) { + LOGGER.error("DataUpdater: unable to process domain changes", ex); + } + } + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStore.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStore.java new file mode 100644 index 00000000000..2b92f570fa4 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStore.java @@ -0,0 +1,348 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.file; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.token.PrincipalToken; +import com.yahoo.athenz.zms.SignedDomain; +import com.yahoo.athenz.zms.SignedDomains; +import com.yahoo.athenz.zms.ZMSClient; +import com.yahoo.athenz.zms.ZMSClientException; +import com.yahoo.athenz.zts.store.ChangeLogStore; +import com.yahoo.rdl.*; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.security.PrivateKey; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A simple implementation of StructStore that simply stores + * the Struct as JSON in its own file. + */ +public class ZMSFileChangeLogStore implements ChangeLogStore { + + private static final Logger LOGGER = LoggerFactory.getLogger(ZMSFileChangeLogStore.class); + + File rootDir = null; + public String lastModTime = null; + + private PrivateKey privateKey = null; + private String privateKeyId = "0"; + private Authority authority = null; + private String zmsUrl = null; + + private static final String ATTR_TAG = "tag"; + private static final String LAST_MOD_FNAME = ".lastModTime"; + private static final String ATTR_LAST_MOD_TIME = "lastModTime"; + private static final String ATHENZ_SYS_DOMAIN = "sys.auth"; + private static final String ATHENZ_ZTS_SERVICE = "zts"; + + private static final String ZTS_PROP_ZMS_URL_OVERRIDE = "athenz.zts.zms_url"; + + public ZMSFileChangeLogStore(String rootDirectory, PrivateKey privateKey, String privateKeyId) { + + // save our private key and authority + + this.privateKey = privateKey; + this.privateKeyId = privateKeyId; + + // setup principal authority for our zms client + + authority = new com.yahoo.athenz.auth.impl.PrincipalAuthority(); + + // check to see if we need to override the ZMS url from the config file + + zmsUrl = System.getProperty(ZTS_PROP_ZMS_URL_OVERRIDE, System.getenv("ZMS_URL")); + + // setup our directory for storing domain files + + rootDir = new File(rootDirectory); + + if (!rootDir.exists()) { + if (!rootDir.mkdirs()) { + error("cannot create specified root: " + rootDirectory); + } + } else { + if (!rootDir.isDirectory()) { + error("specified root is not a directory: " + rootDirectory); + } + } + + // make sure only the user has access + + Path rootPath = rootDir.toPath(); + Set perms = EnumSet.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE); + try { + Files.setPosixFilePermissions(rootPath, perms); + } catch (IOException e) { + error("unable to set directory owner permissions: " + e.getMessage()); + } + + // retrieve our last modification timestamp + + lastModTime = retrieveLastModificationTime(); + + // if we do not have a last modification timestamp then we're going to + // clean up all locally cached domain files + + if (lastModTime == null) { + List localDomains = getLocalDomainList(); + for (String domain : localDomains) { + delete(domain); + } + } + } + + @Override + public boolean supportsFullRefresh() { + return true; + } + + @Override + public SignedDomain getSignedDomain(String domainName) { + + return get(domainName, SignedDomain.class); + } + + @Override + public void removeLocalDomain(String domainName) { + delete(domainName); + } + + @Override + public void saveLocalDomain(String domainName, SignedDomain signedDomain) { + put(domainName, JSON.bytes(signedDomain)); + } + + void setupDomainFile(File file) { + + try { + new FileOutputStream(file).close(); + file.setLastModified(System.currentTimeMillis()); + Path path = file.toPath(); + Set perms = EnumSet.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE); + Files.setPosixFilePermissions(path, perms); + } catch (IOException ex) { + ex.printStackTrace(); + error("unable to setup domain file with permissions: " + ex.getMessage()); + } + } + + public synchronized T get(String name, Class classType) { + + File file = new File(rootDir, name); + if (!file.exists()) { + return null; + } + Path path = Paths.get(file.toURI()); + try { + return JSON.fromBytes(Files.readAllBytes(path), classType); + } catch (IOException ex) { + LOGGER.error("Unable to retrieve domain file: {} error: {}", file.getPath(), ex.getMessage()); + } + return null; + } + + public synchronized void put(String name, byte[] data) { + + File file = new File(rootDir, name); + if (!file.exists()) { + setupDomainFile(file); + } + Path path = Paths.get(file.toURI()); + try { + Files.write(path, data); + } catch (IOException ex) { + ex.printStackTrace(); + error("unable to save domain file: " + file.getPath() + " error: " + ex.getMessage()); + } + } + + public synchronized void delete(String name) { + File file = new File(rootDir, name); + if (!file.exists()) { + return; + } + + try { + Files.delete(file.toPath()); + } catch (Exception exc) { + error("Cannot delete file or directory: " + name + " : exc: " + exc); + } + } + + @Override + public List getLocalDomainList() { + return scan(); + } + + List scan() { + + List names = new ArrayList(); + for (String name : rootDir.list()) { + + // we are going to skip any hidden files + + if (name.charAt(0) != '.') { + names.add(name); + } + } + + return names; + } + + ZMSClient getZMSClient() { + + PrincipalToken token = new PrincipalToken.Builder("S1", ATHENZ_SYS_DOMAIN, ATHENZ_ZTS_SERVICE) + .expirationWindow(24 * 60 * 60L).keyId(privateKeyId).build(); + token.sign(privateKey); + + Principal principal = SimplePrincipal.create(ATHENZ_SYS_DOMAIN, ATHENZ_ZTS_SERVICE, + token.getSignedToken(), authority); + + ZMSClient zmsClient = new ZMSClient(zmsUrl); + zmsClient.addCredentials(principal); + return zmsClient; + } + + @Override + public Set getServerDomainList() { + + Set zmsDomainList = null; + try (ZMSClient zmsClient = getZMSClient()) { + zmsDomainList = new HashSet(zmsClient.getDomainList().getNames()); + } catch (ZMSClientException ex) { + LOGGER.error("Unable to retrieve domain list from ZMS: " + ex.getMessage()); + return null; + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Number of ZMS domains: " + zmsDomainList.size()); + } + + return zmsDomainList; + } + + public String retrieveLastModificationTime() { + Struct lastModStruct = get(LAST_MOD_FNAME, Struct.class); + if (lastModStruct == null) { + return null; + } + return lastModStruct.getString(ATTR_LAST_MOD_TIME); + } + + @Override + public void setLastModificationTimestamp(String newLastModTime) { + + lastModTime = newLastModTime; + if (lastModTime == null) { + delete(LAST_MOD_FNAME); + } else { + + // update the last modification timestamp + + Struct lastModStruct = new Struct(); + lastModStruct.put(ATTR_LAST_MOD_TIME, lastModTime); + put(LAST_MOD_FNAME, JSON.bytes(lastModStruct)); + } + } + + String retrieveTagHeader(Map> responseHeaders) { + + // our tag value is going to be returned from the server in the + // response headers as the value to the key "tag" + + List tagData = responseHeaders.get(ATTR_TAG); + if (tagData == null || tagData.isEmpty()) { + LOGGER.error("Response headers from ZMS does not include 'ETag/tag' value"); + return null; + } + return tagData.get(0); + } + + @Override + public SignedDomains getUpdatedSignedDomains(StringBuilder lastModTimeBuffer) { + + try (ZMSClient zmsClient = getZMSClient()) { + + // request all the changes from ZMS + + Map> responseHeaders = new HashMap>(); + SignedDomains signedDomains = zmsClient.getSignedDomains(lastModTime, null, null, responseHeaders); + + // retrieve the tag value for the request + + String newLastModTime = retrieveTagHeader(responseHeaders); + if (newLastModTime == null) { + return null; + } + + // set the last modification time to be returned to the caller + + lastModTimeBuffer.setLength(0); + lastModTimeBuffer.append(newLastModTime); + + return signedDomains; + + } catch (ZMSClientException e) { + LOGGER.error("Error when refreshing data from ZMS: " + e.getMessage()); + return null; + } + } + + public static void deleteDirectory(File file) { + if (!file.exists()) { + return; + } + + if (file.isDirectory()) { + + File[] fileList = file.listFiles(); + if (fileList != null) { + for (File ff : fileList) { + deleteDirectory(ff); + } + } + } + if (!file.delete()) { + error("Cannot delete file: " + file); + } + } + + static void error(String msg) { + LOGGER.error(msg); + throw new RuntimeException("ZMSFileChangeLogStore: " + msg); + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreFactory.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreFactory.java new file mode 100644 index 00000000000..bce58c6ca3d --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreFactory.java @@ -0,0 +1,35 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.file; + +import java.io.File; +import java.security.PrivateKey; + +import com.yahoo.athenz.zts.store.ChangeLogStore; +import com.yahoo.athenz.zts.store.ChangeLogStoreFactory; +import com.yahoo.athenz.zts.store.CloudStore; + +public class ZMSFileChangeLogStoreFactory implements ChangeLogStoreFactory { + + private static final String ZTS_DATA_STORE = "zts_store"; + + public ChangeLogStore create(String ztsHomeDir, PrivateKey privateKey, + String privateKeyId, CloudStore cloudStore) { + + return new ZMSFileChangeLogStore(ztsHomeDir + File.separator + ZTS_DATA_STORE, privateKey, + privateKeyId); + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStore.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStore.java new file mode 100644 index 00000000000..d3993dbaf35 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStore.java @@ -0,0 +1,234 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.s3; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.GetObjectRequest; +import com.amazonaws.services.s3.model.ListObjectsRequest; +import com.amazonaws.services.s3.model.ObjectListing; +import com.amazonaws.services.s3.model.S3Object; +import com.amazonaws.services.s3.model.S3ObjectSummary; +import com.yahoo.athenz.zms.SignedDomain; +import com.yahoo.athenz.zms.SignedDomains; +import com.yahoo.athenz.zts.store.ChangeLogStore; +import com.yahoo.athenz.zts.store.CloudStore; +import com.yahoo.rdl.JSON; + +public class S3ChangeLogStore implements ChangeLogStore { + + private static final Logger LOGGER = LoggerFactory.getLogger(S3ChangeLogStore.class); + private static final String ZTS_PROP_BUCKET_NAME = "athenz.zts.aws_bucket_name"; + private static final String ZTS_BUCKET_DEFAULT = "athenz-domain-sys.auth"; + + long lastModTime = 0; + CloudStore cloudStore = null; + String s3BucketName = null; + + public S3ChangeLogStore(CloudStore cloudStore) { + this.cloudStore = cloudStore; + s3BucketName = System.getProperty(ZTS_PROP_BUCKET_NAME, ZTS_BUCKET_DEFAULT); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("AWSS3ChangeLog: S3 Bucket name: " + s3BucketName); + } + } + + @Override + public boolean supportsFullRefresh() { + return false; + } + + @Override + public SignedDomain getSignedDomain(String domainName) { + AmazonS3 s3 = getS3Client(); + return getSignedDomain(s3, domainName); + } + + SignedDomain getSignedDomain(AmazonS3 s3, String domainName) { + + SignedDomain signedDomain = null; + try { + S3Object object = s3.getObject(new GetObjectRequest(s3BucketName, domainName)); + if (object == null) { + LOGGER.error("AWSS3ChangeLog: getSignedDomain - domain not found " + domainName); + return null; + } + + BufferedReader reader = new BufferedReader(new InputStreamReader(object.getObjectContent())); + StringBuilder data = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + data.append(line); + } + reader.close(); + signedDomain = JSON.fromString(data.toString(), SignedDomain.class); + } catch (Exception ex) { + LOGGER.error("AWSS3ChangeLog: getSignedDomain - unable to get domain " + domainName + + " error: " + ex.getMessage()); + } + return signedDomain; + } + + @Override + public void removeLocalDomain(String domainName) { + + // in AWS our Athenz syncer is responsible for pushing new + // changes including removing deleted domain to S3 so this + // api is just a no-op + + return; + } + + @Override + public void saveLocalDomain(String domainName, SignedDomain signedDomain) { + + // in AWS our Athenz syncer is responsible for pushing new + // changes into S3 so this api is just a no-op + + return; + } + + /** + * list the objects in the zts bucket. If te mod time is specified as 0 + * then we want to list all objects otherwise, we only list objects + * that are newer than the specified timestamp + * @param s3 AWS S3 client object + * @param domains collection to be updated to include domain names + * @param modTime only include domains newer than this timestamp + */ + void listObjects(AmazonS3 s3, Collection domains, long modTime) { + + ObjectListing objectListing = s3.listObjects(new ListObjectsRequest() + .withBucketName(s3BucketName)); + + String objectName = null; + while (objectListing != null) { + + // process each entry in our result set and add the domain + // name to our return list + + for (S3ObjectSummary objectSummary : objectListing.getObjectSummaries()) { + + // if mod time is specified then make sure we automatically skip + // any domains older than the specified value + + if (modTime > 0 && objectSummary.getLastModified().getTime() <= modTime) { + continue; + } + + // for now skip any folders/objects that start with '.' + + objectName = objectSummary.getKey(); + if (objectName.charAt(0) == '.') { + continue; + } + domains.add(objectName); + } + + // check if the object listing is truncated or not (break out in this case) + // technically we can skip this call and just call listNextBatchOfResults + // since that returns null if the object listing is not truncated but + // this direct check here makes the logic easier to follow + + if (!objectListing.isTruncated()) { + break; + } + + objectListing = s3.listNextBatchOfObjects(objectListing); + } + } + + @Override + public List getLocalDomainList() { + + // check to see if we need to maintain our last modification time. + // this will be necessary if our last mod time field is null. We need + // to save the timestamp at the beginning just in case we end up getting + // paged results and while processing the last page, the Syncer pushes + // updated domains from the earlier pages + + if (lastModTime == 0) { + lastModTime = System.currentTimeMillis(); + } + + ArrayList domains = new ArrayList<>(); + listObjects(getS3Client(), domains, 0); + return domains; + } + + @Override + public Set getServerDomainList() { + + HashSet domains = new HashSet<>(); + listObjects(getS3Client(), domains, 0); + return domains; + } + + @Override + public SignedDomains getUpdatedSignedDomains(StringBuilder lastModTimeBuffer) { + + // We need save the timestamp at the beginning just in case we end up getting + // paged results and while processing the last page, S3 gets pushed + // updated domains from the earlier pages + + lastModTimeBuffer.append(System.currentTimeMillis()); + + // AWS S3 API does not provide support for listing objects filtered + // based on its last modification timestamp so we need to get + // the full list and filter ourselves + + AmazonS3 s3 = getS3Client(); + ArrayList domains = new ArrayList<>(); + listObjects(s3, domains, lastModTime); + + ArrayList signedDomainList = new ArrayList<>(); + SignedDomain signedDomain = null; + for (String domain : domains) { + signedDomain = getSignedDomain(s3, domain); + if (signedDomain != null) { + signedDomainList.add(signedDomain); + } + } + + SignedDomains signedDomains = new SignedDomains(); + signedDomains.setDomains(signedDomainList); + return signedDomains; + } + + @Override + public void setLastModificationTimestamp(String newLastModTime) { + if (newLastModTime == null) { + lastModTime = 0; + } else { + lastModTime = Long.parseLong(newLastModTime); + } + } + + AmazonS3 getS3Client() { + return cloudStore.getS3Client(); + } +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreFactory.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreFactory.java new file mode 100644 index 00000000000..a31a2bd49b0 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreFactory.java @@ -0,0 +1,33 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.s3; + +import java.security.PrivateKey; + +import com.yahoo.athenz.zts.store.ChangeLogStore; +import com.yahoo.athenz.zts.store.ChangeLogStoreFactory; +import com.yahoo.athenz.zts.store.CloudStore; + +public class S3ChangeLogStoreFactory implements ChangeLogStoreFactory { + + @Override + public ChangeLogStore create(String ztsHomeDir, PrivateKey privateKey, + String privateKeyId, CloudStore cloudStore) { + + return new S3ChangeLogStore(cloudStore); + } + +} diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/utils/ZTSUtils.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/utils/ZTSUtils.java new file mode 100644 index 00000000000..e04e199d989 --- /dev/null +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/utils/ZTSUtils.java @@ -0,0 +1,221 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.utils; + +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.auth.util.CryptoException; +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.zts.Identity; +import com.yahoo.athenz.zts.ZTSConsts; +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.store.DataStore; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +public class ZTSUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(DataStore.class); + + public static final String ZTS_DEFAULT_EXCLUDED_CIPHER_SUITES = "SSL_RSA_WITH_DES_CBC_SHA," + + "SSL_DHE_RSA_WITH_DES_CBC_SHA,SSL_DHE_DSS_WITH_DES_CBC_SHA," + + "SSL_RSA_EXPORT_WITH_RC4_40_MD5,SSL_RSA_EXPORT_WITH_DES40_CBC_SHA," + + "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"; + public static final String ZTS_DEFAULT_EXCLUDED_PROTOCOLS = "SSLv2,SSLv3"; + + public static int retrieveConfigSetting(String property, int defaultValue) { + + int settingValue; + try { + String propValue = System.getProperty(property); + if (propValue == null) { + return defaultValue; + } + + settingValue = Integer.parseInt(propValue); + + if (settingValue <= 0) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Invalid " + property + " value: " + propValue + + ", defaulting to " + defaultValue + " seconds"); + } + settingValue = defaultValue; + } + } catch (Exception ex) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Invalid " + property + " value, defaulting to " + + defaultValue + " seconds: " + ex.getMessage()); + } + settingValue = defaultValue; + } + + return settingValue; + } + + public static SslContextFactory createSSLContextObject(String[] clientProtocols) { + + String keyStorePath = System.getProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PATH); + String keyStorePassword = System.getProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PASSWORD); + String keyStoreType = System.getProperty(ZTSConsts.ZTS_PROP_KEYSTORE_TYPE, "PKCS12"); + String keyManagerPassword = System.getProperty(ZTSConsts.ZTS_PROP_KEYMANAGER_PASSWORD); + String trustStorePath = System.getProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PATH); + String trustStorePassword = System.getProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PASSWORD); + String trustStoreType = System.getProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + String excludedCipherSuites = System.getProperty(ZTSConsts.ZTS_PROP_EXCLUDED_CIPHER_SUITES, + ZTS_DEFAULT_EXCLUDED_CIPHER_SUITES); + String excludedProtocols = System.getProperty(ZTSConsts.ZTS_PROP_EXCLUDED_PROTOCOLS, + ZTS_DEFAULT_EXCLUDED_PROTOCOLS); + Boolean wantClientAuth = Boolean.parseBoolean(System.getProperty(ZTSConsts.ZTS_PROP_WANT_CLIENT_CERT, "false")); + + SslContextFactory sslContextFactory = new SslContextFactory(); + if (keyStorePath != null) { + LOGGER.info("createSSLContextObject: using SSL KeyStore path: " + keyStorePath); + sslContextFactory.setKeyStorePath(keyStorePath); + } + if (keyStorePassword != null) { + sslContextFactory.setKeyStorePassword(keyStorePassword); + } + sslContextFactory.setKeyStoreType(keyStoreType); + + if (keyManagerPassword != null) { + sslContextFactory.setKeyManagerPassword(keyManagerPassword); + } + if (trustStorePath != null) { + LOGGER.info("createSSLContextObject: using SSL TrustStore path: " + trustStorePath); + sslContextFactory.setTrustStorePath(trustStorePath); + } + if (trustStorePassword != null) { + sslContextFactory.setTrustStorePassword(trustStorePassword); + } + sslContextFactory.setTrustStoreType(trustStoreType); + + if (excludedCipherSuites.length() != 0) { + sslContextFactory.setExcludeCipherSuites(excludedCipherSuites.split(",")); + } + + if (excludedProtocols.length() != 0) { + sslContextFactory.setExcludeProtocols(excludedProtocols.split(",")); + } + + sslContextFactory.setWantClientAuth(wantClientAuth); + if (clientProtocols != null) { + sslContextFactory.setIncludeProtocols(clientProtocols); + } + + return sslContextFactory; + } + + public static final boolean emitMonmetricError(int errorCode, String caller, String domainName, Metric metric) { + + if (errorCode < 1) { + return false; + } + if (caller == null || caller.length() == 0) { + return false; + } + caller = caller.trim(); + String alphanum = "^[a-zA-Z0-9]*$"; + if (!caller.matches(alphanum)) { + return false; + } + + // Set 3 scoreboard error metrics: + // (1) cumulative "ERROR" (of all zts request and error types) + // (2) cumulative granular zts request and error type (eg- + // "getdomainlist_error_400") + // (3) cumulative error type (of all zts requests) (eg- "error_404") + String errCode = Integer.toString(errorCode); + metric.increment("ERROR"); + if (domainName != null) { + metric.increment(caller.toLowerCase() + "_error_" + errCode, domainName); + } else { + metric.increment(caller.toLowerCase() + "_error_" + errCode); + } + metric.increment("error_" + errCode); + + return true; + } + + public static boolean validateCertificateRequest(PKCS10CertificationRequest certReq, String serviceYrn) { + String cnCertReq = null; + try { + LdapName ldapDN = new LdapName(certReq.getSubject().toString()); + for (Rdn rdn: ldapDN.getRdns()) { + if (rdn.getType().equalsIgnoreCase("cn")) { + cnCertReq = (String) rdn.getValue(); + break; + } + } + } catch (InvalidNameException ex) { + LOGGER.error("YSvcCertStore::validateCertificateRequest - unable to extract csr cn", ex); + return false; + } + + if (cnCertReq == null) { + LOGGER.error("YSvcCertStore::validateCertificateRequest - unable to extract csr cn: " + + certReq.toString()); + return false; + } + + if (!cnCertReq.equalsIgnoreCase(serviceYrn)) { + LOGGER.error("YSvcCertStore::validateCertificateRequest - cn mismatch: " + + cnCertReq + " vs. " + serviceYrn); + return false; + } + + return true; + } + + public static Identity generateIdentity(CertSigner certSigner, String csr, String serviceYrn, + String caPEMCertificate) { + + // first we need to validate our csr to make sure + // it contains the right common name + + try { + PKCS10CertificationRequest certReq = Crypto.getPKCS10CertRequest(csr); + if (certReq == null) { + LOGGER.error("YSvcCertStore::generateIdentity: unable to parse PKCS10 cert request"); + return null; + } + + if (!validateCertificateRequest(certReq, serviceYrn)) { + LOGGER.error("YSvcCertStore::generateIdentity: unable to validate PKCS10 cert request"); + return null; + } + + } catch (CryptoException ex) { + LOGGER.error("generateIdentity: unable to generate identity certificate: ", ex); + return null; + } + + // generate a certificate for this certificate request + + String pemCert = certSigner.generateX509Certificate(csr); + if (pemCert == null || pemCert.isEmpty()) { + LOGGER.error("generateIdentity: CertSigner was unable to generate X509 certificate"); + return null; + } + + return new Identity().setName(serviceYrn).setCertificate(pemCert).setCaCertBundle(caPEMCertificate); + } +} diff --git a/servers/zts/src/main/rdl/ZTS.rdl b/servers/zts/src/main/rdl/ZTS.rdl new file mode 100644 index 00000000000..c33cf80772b --- /dev/null +++ b/servers/zts/src/main/rdl/ZTS.rdl @@ -0,0 +1,17 @@ +// Copyright 2016 Yahoo Inc. +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +// +// The Authorization Token Service (ZTS) API +// +name ZTS; +use "rdl"; +version 1; +namespace com.yahoo.athenz.zts; + +include "../../../../../core/zts/src/main/rdl/ZTS.rdl"; + +// Get RDL Schema +resource rdl.Schema GET "/schema" { + expected OK; +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ResourceExceptionTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ResourceExceptionTest.java new file mode 100644 index 00000000000..bd74898726e --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ResourceExceptionTest.java @@ -0,0 +1,69 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import static org.testng.Assert.*; + +import org.testng.annotations.Test; + +public class ResourceExceptionTest { + + @Test + public void testCodeToString() { + + assertEquals("OK", ResourceException.codeToString(200)); + assertEquals("Created", ResourceException.codeToString(201)); + assertEquals("Accepted", ResourceException.codeToString(202)); + assertEquals("No Content", ResourceException.codeToString(204)); + assertEquals("Moved Permanently", ResourceException.codeToString(301)); + assertEquals("Found", ResourceException.codeToString(302)); + assertEquals("See Other", ResourceException.codeToString(303)); + assertEquals("Not Modified", ResourceException.codeToString(304)); + assertEquals("Temporary Redirect", ResourceException.codeToString(307)); + assertEquals("Bad Request", ResourceException.codeToString(400)); + assertEquals("Unauthorized", ResourceException.codeToString(401)); + assertEquals("Forbidden", ResourceException.codeToString(403)); + assertEquals("Not Found", ResourceException.codeToString(404)); + assertEquals("Conflict", ResourceException.codeToString(409)); + assertEquals("Gone", ResourceException.codeToString(410)); + assertEquals("Precondition Failed", ResourceException.codeToString(412)); + assertEquals("Unsupported Media Type", ResourceException.codeToString(415)); + assertEquals("Internal Server Error", ResourceException.codeToString(500)); + assertEquals("Not Implemented", ResourceException.codeToString(501)); + assertEquals("1001", ResourceException.codeToString(1001)); + } + + @Test + public void testCodeOnly() { + + ResourceException exc = new ResourceException(400); + assertEquals(exc.getData().toString(), "{code: 400, message: \"Bad Request\"}"); + } + + @Test + public void testGetData() { + + ResourceException exc = new ResourceException(400, "Invalid domain name"); + assertEquals(exc.getData(), "Invalid domain name"); + } + + @Test + public void testGetDataCast() { + + ResourceException exc = new ResourceException(400, new Integer(5000)); + assertEquals(exc.getData(Integer.class), new Integer(5000)); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ResultObjectTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ResultObjectTest.java new file mode 100644 index 00000000000..0c8e69e2313 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ResultObjectTest.java @@ -0,0 +1,58 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import static org.testng.Assert.*; + +import javax.ws.rs.WebApplicationException; + +import org.testng.annotations.Test; + +public class ResultObjectTest { + + @Test + public void testDomainSignedPolicyDataResult() { + GetDomainSignedPolicyDataResult object = new GetDomainSignedPolicyDataResult(null); + assertFalse(object.isAsync()); + + try { + object.done(101); + fail(); + } catch (WebApplicationException ex) { + } + } + + @Test + public void testDoneException() { + GetDomainSignedPolicyDataResult object = new GetDomainSignedPolicyDataResult(null); + DomainSignedPolicyData data = new DomainSignedPolicyData().setKeyId("test"); + try { + object.done(101, data, "test"); + fail(); + } catch (WebApplicationException ex) { + } + } + + @Test + public void testDoneException2() { + GetDomainSignedPolicyDataResult object = new GetDomainSignedPolicyDataResult(null); + try { + object.done(101, "test"); + fail(); + } catch (WebApplicationException ex) { + } + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/RsrcCtxWrapperTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/RsrcCtxWrapperTest.java new file mode 100644 index 00000000000..5cb75cc2ae3 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/RsrcCtxWrapperTest.java @@ -0,0 +1,131 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import static org.testng.Assert.*; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Authorizer; +import com.yahoo.athenz.auth.Principal; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import com.yahoo.athenz.common.server.rest.Http.AuthorityList; + +public class RsrcCtxWrapperTest { + + @Test + public void TestRsrcCtxWrapperSimpleAssertion() { + HttpServletRequest reqMock = Mockito.mock(HttpServletRequest.class); + HttpServletResponse resMock = Mockito.mock(HttpServletResponse.class); + + AuthorityList authListMock = new AuthorityList(); + Authorizer authorizerMock = Mockito.mock(Authorizer.class); + Authority authMock = Mockito.mock(Authority.class); + + Principal prin = Mockito.mock(Principal.class); + + Mockito.when(authMock.getHeader()).thenReturn("testheader"); + Mockito.when(reqMock.getHeader("testheader")).thenReturn("testcred"); + Mockito.when(authMock.getCredSource()).thenReturn(com.yahoo.athenz.auth.Authority.CredSource.HEADER); + Mockito.when(authMock.authenticate(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any())) + .thenReturn(prin); + Mockito.when(reqMock.getRemoteAddr()).thenReturn("1.1.1.1"); + Mockito.when(reqMock.getMethod()).thenReturn("POST"); + authListMock.add(authMock); + + RsrcCtxWrapper wrapper = new RsrcCtxWrapper(reqMock, resMock, authListMock, authorizerMock); + + assertNotNull(wrapper.context()); + + // default principal should be null + assertEquals(wrapper.principal(), null); + + assertEquals(wrapper.request(), reqMock); + assertEquals(wrapper.response(), resMock); + + wrapper.authenticate(); + + // after authenticate, principal should be set + assertEquals(wrapper.principal(), prin); + + // invalid kerberos request + try { + wrapper.authenticateKerberos(); + fail(); + } catch (ResourceException ex) { + assertNotNull(ex); + } + } + + @Test + public void TestAuthorize() { + HttpServletRequest reqMock = Mockito.mock(HttpServletRequest.class); + HttpServletResponse resMock = Mockito.mock(HttpServletResponse.class); + + AuthorityList authListMock = new AuthorityList(); + Authorizer authorizerMock = Mockito.mock(Authorizer.class); + Authority authMock = Mockito.mock(Authority.class); + + Principal prin = Mockito.mock(Principal.class); + + Mockito.when(authMock.getHeader()).thenReturn("testheader"); + Mockito.when(reqMock.getHeader("testheader")).thenReturn("testcred"); + Mockito.when(authMock.getCredSource()).thenReturn(com.yahoo.athenz.auth.Authority.CredSource.HEADER); + Mockito.when(authMock.authenticate(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any())) + .thenReturn(prin); + Mockito.when(reqMock.getRemoteAddr()).thenReturn("1.1.1.1"); + Mockito.when(reqMock.getMethod()).thenReturn("POST"); + authListMock.add(authMock); + + // force true access right + Mockito.when(authorizerMock.access(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any())) + .thenReturn(true); + + RsrcCtxWrapper wrapper = new RsrcCtxWrapper(reqMock, resMock, authListMock, authorizerMock); + + wrapper.authorize("add-domain", "test", "test"); + + // after authorize success, principal should be set + assertEquals(wrapper.principal(), prin); + } + + @Test(expectedExceptions = { ResourceException.class }) + public void TestAuthorizeInvalid() { + HttpServletRequest reqMock = Mockito.mock(HttpServletRequest.class); + HttpServletResponse resMock = Mockito.mock(HttpServletResponse.class); + + AuthorityList authListMock = new AuthorityList(); + Authorizer authorizerMock = Mockito.mock(Authorizer.class); + + Mockito.when(reqMock.getHeader("testheader")).thenReturn("testcred"); + Mockito.when(reqMock.getRemoteAddr()).thenReturn("1.1.1.1"); + Mockito.when(reqMock.getMethod()).thenReturn("POST"); + + // force true access right + Mockito.when(authorizerMock.access(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any())) + .thenReturn(true); + + RsrcCtxWrapper wrapper = new RsrcCtxWrapper(reqMock, resMock, authListMock, authorizerMock); + + // when not set authority + wrapper.authorize("add-domain", "test", "test"); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSBinderTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSBinderTest.java new file mode 100644 index 00000000000..5d5602414b7 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSBinderTest.java @@ -0,0 +1,39 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import static org.testng.Assert.*; +import org.mockito.Mockito; + +import org.testng.annotations.Test; + +public class ZTSBinderTest { + + @Test + public void testZTSBinder() { + ZTSImpl handlerMock = Mockito.mock(ZTSImpl.class); + ZTSBinder binder = new ZTSBinder(handlerMock); + + assertTrue(binder.toString().contains("Binder:")); + } + + @Test(expectedExceptions = { IllegalArgumentException.class }) + public void testZTSBinderConfigure() { + // shouldn't accessed configure + ZTSImpl handlerMock = Mockito.mock(ZTSImpl.class); + new ZTSBinder(handlerMock).configure(); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSDaemonTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSDaemonTest.java new file mode 100644 index 00000000000..cebcfd4c2c7 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSDaemonTest.java @@ -0,0 +1,98 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import org.apache.commons.daemon.DaemonContext; +import org.mockito.Mockito; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.zts.cert.CertSigner; + +public class ZTSDaemonTest { + + @Test + public void testZTSDaemon() throws Exception { + + ZTSDaemon daemon = new ZTSDaemon(); + + DaemonContext ctxMock = Mockito.mock(DaemonContext.class); + Mockito.when(ctxMock.getArguments()).thenReturn(null); + + daemon.init(ctxMock); + + // nothing to do + daemon.start(); + daemon.stop(); + daemon.destroy(); + } + + @Test + public void testZTSGetAuditLogger() { + AuditLogger logger = ZTS.getAuditLogger(); + assertNotNull(logger); + } + + @Test + public void testZTSGetAuthority() { + assertNull(ZTS.getAuthority("test")); + } + + @Test + public void testZTSGetServerHostName() { + assertNotNull(ZTS.getServerHostName()); + } + + @Test + public void testZTSGetPortNumber() { + + // default + assertEquals(ZTS.getPortNumber("unsetproperty", 4080), 4080); + + // set appropriate property + System.setProperty("testportnum", "4444"); + assertEquals(ZTS.getPortNumber("testportnum", 4080), 4444); + + // set invalid port number -> should set default + System.setProperty("testportnum", "70000"); + assertEquals(ZTS.getPortNumber("testportnum", 4080), 4080); + + } + + @Test + public void testZTSGetCertSigner() { + assertNotNull(ZTS.getCertSigner()); + } + + @Test + public void testZTSGetSvcCertStore() { + CertSigner signer = ZTS.getCertSigner(); + + assertNotNull(ZTS.getSvcCertStore(signer)); + } + + @Test + public void testZTSGetMetric() { + assertNotNull(ZTS.getMetric()); + } + + @Test + public void testZTSGetPrivateKeyStore() { + assertNotNull(ZTS.getPrivateKeyStore("localhost")); + } + +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java new file mode 100644 index 00000000000..fff61947a83 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java @@ -0,0 +1,3662 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.TimeUnit; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.Authority; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.server.log.AuditLogFactory; +import com.yahoo.athenz.common.server.log.AuditLogMsgBuilder; +import com.yahoo.athenz.common.server.log.AuditLogger; +import com.yahoo.athenz.common.utils.SignUtils; +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.AssertionEffect; +import com.yahoo.athenz.zms.DomainData; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.ServiceIdentity; +import com.yahoo.athenz.zms.SignedDomain; +import com.yahoo.athenz.zts.ZTSAuthorizer.AccessStatus; +import com.yahoo.athenz.zts.cache.DataCache; +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.cert.MockCertSigner; +import com.yahoo.athenz.zts.cert.MockCertSignerFactory; +import com.yahoo.athenz.zts.cert.SvcCertStore; +import com.yahoo.athenz.zts.cert.impl.YSvcCertStore; +import com.yahoo.athenz.zts.store.ChangeLogStore; +import com.yahoo.athenz.zts.store.CloudStore; +import com.yahoo.athenz.zts.store.CloudStoreTest; +import com.yahoo.athenz.zts.store.DataStore; +import com.yahoo.athenz.zts.store.MockCloudStore; +import com.yahoo.athenz.zts.store.file.MockZMSFileChangeLogStore; +import com.yahoo.athenz.zts.store.file.ZMSFileChangeLogStore; +import com.yahoo.athenz.zts.utils.ZTSUtils; +import com.yahoo.rdl.Schema; +import com.yahoo.rdl.Timestamp; + +public class ZTSImplTest { + + int roleTokenDefaultTimeout = 2400; + int roleTokenMaxTimeout = 96000; + + ZTSImpl zts = null; + ZTSAuthorizer authorizer = null; + DataStore store = null; + PrivateKey privateKey = null; + PublicKey publicKey = null; + + static final String ZTS_DATA_STORE_PATH = "/tmp/zts_server_unit_tests/zts_root"; + static final String ZTS_Y64_CERT0 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RR" + + "RUJBUVVBQTRHTkFEQ0JpUUtCZ1FDMXRHU1ZDQTh3bDVldzVZNzZXajJySkFVRApZYW5FSmZLbUFseDVjUS84a" + + "EtFVWZTU2dwWHIzQ3pkaDFhMjZkbGI3bW1LMjlxbVhKWGg2dW1XOUF5ZlRPS1ZvCis2QVNsb1ZVM2F2dnVmbE" + + "dVT0VnMmpzbWRha1IyNEtjTGpBdTZRclVlNDE3bEczdDhxU1BJR2pTNUMrQ3NKVXcKaDA0aEh4NWYrUEV3eFY" + + "0cmJRSURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + static final String ZTS_PEM_CERT0 = "-----BEGIN PUBLIC KEY-----\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1tGSVCA8wl5ew5Y76Wj2rJAUD\n" + + "YanEJfKmAlx5cQ/8hKEUfSSgpXr3Czdh1a26dlb7mmK29qmXJXh6umW9AyfTOKVo\n" + + "+6ASloVU3avvuflGUOEg2jsmdakR24KcLjAu6QrUe417lG3t8qSPIGjS5C+CsJUw\n" + + "h04hHx5f+PEwxV4rbQIDAQAB\n" + + "-----END PUBLIC KEY-----\n"; + final static String AWS_INSTANCE_DOCUMENT_WOUT_TIMESTAMP_BEGIN = "{\n" + + " \"devpayProductCodes\" : null,\n" + + " \"availabilityZone\" : \"us-west-2a\",\n" + + " \"privateIp\" : \"10.10.10.10\",\n" + + " \"version\" : \"2010-08-31\",\n" + + " \"instanceId\" : \"i-056921225f1fbb47a\",\n" + + " \"billingProducts\" : null,\n" + + " \"instanceType\" : \"t2.micro\",\n" + + " \"accountId\" : \"111111111111\",\n" + + " \"pendingTime\" : \""; + final static String AWS_INSTANCE_DOCUMENT_WOUT_TIMESTAMP_END = "\",\n" + + " \"imageId\" : \"ami-c229c0a2\",\n" + + " \"architecture\" : \"x86_64\",\n" + + " \"kernelId\" : null,\n" + + " \"ramdiskId\" : null,\n" + + " \"region\" : \"us-west-2\"\n" + + "}"; + + private static final String MOCKCLIENTADDR = "10.11.12.13"; + @Mock HttpServletRequest mockServletRequest; + @Mock HttpServletResponse mockServletResponse; + + static class ZtsMetricTester extends com.yahoo.athenz.common.metrics.impl.NoOpMetric { + Map metrixMap = new HashMap<>(); + + public Map getMap() { return metrixMap; } + + public void increment(String metric, String domainName, int count) { + String key = metric + domainName; + metrixMap.put(key, count); + } + } + + ResourceContext createResourceContext(Principal principal) { + com.yahoo.athenz.common.server.rest.ResourceContext rsrcCtx = Mockito.mock(com.yahoo.athenz.common.server.rest.ResourceContext.class); + Mockito.when(rsrcCtx.principal()).thenReturn(principal); + Mockito.when(rsrcCtx.request()).thenReturn(mockServletRequest); + + RsrcCtxWrapper rsrcCtxWrapper = Mockito.mock(RsrcCtxWrapper.class); + Mockito.when(rsrcCtxWrapper.context()).thenReturn(rsrcCtx); + Mockito.when(rsrcCtxWrapper.principal()).thenReturn(principal); + Mockito.when(rsrcCtxWrapper.request()).thenReturn(mockServletRequest); + Mockito.when(rsrcCtxWrapper.response()).thenReturn(mockServletResponse); + return rsrcCtxWrapper; + } + + ResourceContext createResourceContext(Principal principal, HttpServletRequest request) { + if (request == null) { + return createResourceContext(principal); + } + + com.yahoo.athenz.common.server.rest.ResourceContext rsrcCtx = Mockito.mock(com.yahoo.athenz.common.server.rest.ResourceContext.class); + Mockito.when(rsrcCtx.principal()).thenReturn(principal); + Mockito.when(rsrcCtx.request()).thenReturn(request); + + RsrcCtxWrapper rsrcCtxWrapper = Mockito.mock(RsrcCtxWrapper.class); + Mockito.when(rsrcCtxWrapper.context()).thenReturn(rsrcCtx); + Mockito.when(rsrcCtxWrapper.principal()).thenReturn(principal); + Mockito.when(rsrcCtxWrapper.request()).thenReturn(request); + Mockito.when(rsrcCtxWrapper.response()).thenReturn(mockServletResponse); + return rsrcCtxWrapper; + } + + Object getWebAppExcEntity(javax.ws.rs.WebApplicationException wex) { + javax.ws.rs.core.Response resp = wex.getResponse(); + return resp.getEntity(); + } + + Object getWebAppExcMapValue(javax.ws.rs.WebApplicationException wex, String header) { + javax.ws.rs.core.MultivaluedMap mvmap = wex.getResponse().getMetadata(); + Object obj = mvmap.getFirst(header); + return obj; + } + + public static Role createRoleObject(String domainName, String roleName, + String trust) { + Role role = new Role(); + role.setName(domainName + ":role." + roleName); + role.setTrust(trust); + return role; + } + + public static Role createRoleObject(String domainName, String roleName, + String trust, String member1, String member2) { + + List members = new ArrayList(); + if (member1 != null) { + members.add(member1); + } + if (member2 != null) { + members.add(member2); + } + return createRoleObject(domainName, roleName, trust, members); + } + + public static Role createRoleObject(String domainName, String roleName, + String trust, List members) { + + Role role = new Role(); + role.setName(domainName + ":role." + roleName); + role.setMembers(members); + if (trust != null) { + role.setTrust(trust); + } + + return role; + } + + private Policy createPolicyObject(String domainName, String policyName, + String roleName, boolean generateRoleName, String action, + String resource, AssertionEffect effect) { + + Policy policy = new Policy(); + policy.setName(domainName + ":policy." + policyName); + + Assertion assertion = new Assertion(); + assertion.setAction(action); + assertion.setEffect(effect); + assertion.setResource(resource); + if (generateRoleName) { + assertion.setRole(domainName + ":role." + roleName); + } else { + assertion.setRole(roleName); + } + + List assertList = new ArrayList(); + assertList.add(assertion); + + policy.setAssertions(assertList); + return policy; + } + + @BeforeClass + public void setUpClass() throws Exception { + MockitoAnnotations.initMocks(this); + Mockito.when(mockServletRequest.getRemoteAddr()).thenReturn(MOCKCLIENTADDR); + + System.setProperty(ZTSConsts.ZTS_PROP_METRIC_FACTORY_CLASS, ZTSConsts.ZTS_METRIC_FACTORY_CLASS); + System.setProperty(ZTSConsts.ZTS_PROP_STATS_ENABLED, "true"); + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY_STORE_CLASS, + "com.yahoo.athenz.zts.pkey.file.FilePrivateKeyStoreFactory"); + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY, "src/test/resources/zts_private.pem"); + System.setProperty(ZTSConsts.ZTS_PROP_ATHENZ_CONF, "src/test/resources/athenz.conf"); + } + + @BeforeMethod + public void setup() { + + // we want to make sure we start we clean dir structure + + ZMSFileChangeLogStore.deleteDirectory(new File(ZTS_DATA_STORE_PATH)); + + String privKeyName = System.getProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY); + System.out.println("private key file=" + privKeyName); + File privKeyFile = new File(privKeyName); + String privKey = Crypto.encodedFile(privKeyFile); + + privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + + /* create our data store */ + + roleTokenDefaultTimeout = 2400; + System.setProperty(ZTSConsts.ZTS_PROP_ROLE_TOKEN_DEFAULT_TIMEOUT, + Integer.toString(roleTokenDefaultTimeout)); + + roleTokenMaxTimeout = 96000; + System.setProperty(ZTSConsts.ZTS_PROP_ROLE_TOKEN_MAX_TIMEOUT, + Integer.toString(roleTokenMaxTimeout)); + + System.setProperty(ZTSConsts.ZTS_PROP_AUTHORIZED_PROXY_USERS, + "user_domain.proxy-user1,user_domain.proxy-user2"); + + ChangeLogStore structStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + privateKey, "0"); + + CloudStore cloudStore = new CloudStore(null); + cloudStore.setHttpClient(null); + + CertSigner certSigner = new MockCertSignerFactory().create(null); + SvcCertStore svcCertStore = new YSvcCertStore(certSigner); + + store = new DataStore(structStore, cloudStore); + + com.yahoo.athenz.common.metrics.Metric metric = getMetric(); + zts = new ZTSImpl("localhost", store, cloudStore, svcCertStore, metric, + privateKey, "0", AuditLogFactory.getLogger(), null); + authorizer = (ZTSAuthorizer) zts.getAuthorizer(); + } + + private Metric getMetric(){ + com.yahoo.athenz.common.metrics.MetricFactory metricFactory = null; + com.yahoo.athenz.common.metrics.Metric metric = null; + try { + metricFactory = (com.yahoo.athenz.common.metrics.MetricFactory) + Class.forName(System.getProperty(ZTSConsts.ZTS_PROP_METRIC_FACTORY_CLASS)).newInstance(); + metric = metricFactory.create(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException exc) { + System.out.println("Invalid MetricFactory class: " + ZTSConsts.ZTS_METRIC_FACTORY_CLASS + + " error: " + exc.getMessage()); + metric = new com.yahoo.athenz.common.metrics.impl.NoOpMetric(); + } + return metric; + } + + @AfterMethod + public void shutdown() { + ZMSFileChangeLogStore.deleteDirectory(new File(ZTS_DATA_STORE_PATH)); + System.clearProperty(ZTSConsts.ZTS_PROP_ROLE_TOKEN_MAX_TIMEOUT); + System.clearProperty(ZTSConsts.ZTS_PROP_ROLE_TOKEN_DEFAULT_TIMEOUT); + + } + + private String generateRoleName(String domain, String role) { + StringBuilder str = new StringBuilder(256); + str.append(domain); + str.append(":role."); + str.append(role); + return str.toString(); + } + + private String generatePolicyName(String domain, String policy) { + StringBuilder str = new StringBuilder(256); + str.append(domain); + str.append(":policy."); + str.append(policy); + return str.toString(); + } + + private String generateServiceIdentityName(String domain, String service) { + StringBuilder str = new StringBuilder(256); + str.append(domain); + str.append("."); + str.append(service); + return str.toString(); + } + + private SignedDomain createSignedDomain(String domainName, String tenantDomain, + String serviceName, boolean includeServices) { + + List writers = new ArrayList<>(); + writers.add("user_domain.user"); + writers.add("user_domain.user1"); + + List readers = new ArrayList<>(); + readers.add("user_domain.user3"); + readers.add("user_domain.user4"); + readers.add("user_domain.user1"); + + return createSignedDomain(domainName, tenantDomain, serviceName, writers, + readers, includeServices); + } + + private SignedDomain createSignedDomain(String domainName, String tenantDomain, + String serviceName, List writers, List readers, + boolean includeServices) { + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName(generateRoleName(domainName, "admin")); + List members = new ArrayList<>(); + members.add("user_domain.adminuser"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, "writers")); + role.setMembers(writers); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, "readers")); + role.setMembers(readers); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, "tenant.readers")); + role.setTrust(tenantDomain); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain + ".admin")); + role.setTrust(tenantDomain); + roles.add(role); + + List services = new ArrayList<>(); + + if (includeServices) { + + ServiceIdentity service = new ServiceIdentity(); + service.setName(generateServiceIdentityName(domainName, serviceName)); + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + + List hosts = new ArrayList<>(); + hosts.add("host1"); + hosts.add("host2"); + service.setHosts(hosts); + services.add(service); + + service = new ServiceIdentity(); + service.setName(generateServiceIdentityName(domainName, "backup")); + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + + hosts = new ArrayList<>(); + hosts.add("host2"); + hosts.add("host3"); + service.setHosts(hosts); + services.add(service); + } + + List policies = new ArrayList<>(); + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + ":tenant." + tenantDomain + ".*"); + assertion.setAction("read"); + assertion.setRole(generateRoleName(domainName, "tenant.readers")); + + List assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, "tenant.reader")); + policies.add(policy); + + // tenant admin domain + + policy = new com.yahoo.athenz.zms.Policy(); + assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + ":service." + serviceName + ".tenant." + tenantDomain + ".*"); + assertion.setAction("read"); + assertion.setRole(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain + ".admin")); + + assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, serviceName + ".tenant." + tenantDomain + ".admin")); + policies.add(policy); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain(domainName); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), privateKey)); + signedPolicies.setKeyId("0"); + + DomainData domain = new DomainData(); + domain.setName(domainName); + domain.setRoles(roles); + domain.setServices(services); + domain.setPolicies(signedPolicies); + domain.setModified(Timestamp.fromCurrentTime()); + + signedDomain.setDomain(domain); + + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), privateKey)); + signedDomain.setKeyId("0"); + + return signedDomain; + } + + private SignedDomain createMultipleSignedDomains(String domainName, String tenantDomain1, + String tenantDomain2, String serviceName, boolean includeServices) { + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName(generateRoleName(domainName, "admin")); + List members = new ArrayList<>(); + members.add("user_domain.adminuser"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain1 + ".admin")); + role.setTrust(tenantDomain1); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain2 + ".admin")); + role.setTrust(tenantDomain2); + roles.add(role); + + List services = new ArrayList<>(); + + if (includeServices) { + + ServiceIdentity service = new ServiceIdentity(); + service.setName(generateServiceIdentityName(domainName, serviceName)); + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + + List hosts = new ArrayList<>(); + hosts.add("host1"); + hosts.add("host2"); + service.setHosts(hosts); + services.add(service); + } + + List policies = new ArrayList<>(); + + // tenant admin domain + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + ":service." + serviceName + ".tenant." + tenantDomain1 + ".*"); + assertion.setAction("read"); + assertion.setRole(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain1 + ".admin")); + + List assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, serviceName + ".tenant." + tenantDomain1 + ".admin")); + policies.add(policy); + + policy = new com.yahoo.athenz.zms.Policy(); + assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + ":service." + serviceName + ".tenant." + tenantDomain2 + ".*"); + assertion.setAction("read"); + assertion.setRole(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain2 + ".admin")); + + assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, serviceName + ".tenant." + tenantDomain2 + ".admin")); + policies.add(policy); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain(domainName); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), privateKey)); + signedPolicies.setKeyId("0"); + + DomainData domain = new DomainData(); + domain.setName(domainName); + domain.setRoles(roles); + domain.setServices(services); + domain.setPolicies(signedPolicies); + domain.setModified(Timestamp.fromCurrentTime()); + + signedDomain.setDomain(domain); + + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), privateKey)); + signedDomain.setKeyId("0"); + + return signedDomain; + } + + private SignedDomain createTenantSignedDomain(String domainName, String providerDomain, String providerService) { + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName(generateRoleName(domainName, "admin")); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, "tenancy." + providerDomain + "." + providerService + ".admin")); + members = new ArrayList<>(); + members.add("user_domain.user100"); + members.add("user_domain.user101"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, "readers")); + members = new ArrayList<>(); + members.add("user_domain.user100"); + members.add("user_domain.user101"); + role.setMembers(members); + roles.add(role); + + ServiceIdentity service = new ServiceIdentity(); + service.setName(domainName + ".storage"); + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + + List hosts = new ArrayList<>(); + hosts.add("host1"); + hosts.add("host2"); + service.setHosts(hosts); + + List services = new ArrayList<>(); + services.add(service); + + List policies = new ArrayList<>(); + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(generateRoleName(providerDomain, "tenant.readers")); + assertion.setAction("assume_role"); + assertion.setRole(generateRoleName(domainName, "readers")); + + List assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, "tenancy.readers")); + policies.add(policy); + + policy = new com.yahoo.athenz.zms.Policy(); + assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(generateRoleName(providerDomain, providerService + ".tenant." + domainName + ".admin")); + assertion.setAction("assume_role"); + assertion.setRole(generateRoleName(domainName, "tenancy." + providerDomain + "." + providerService + ".admin")); + + assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, "tenancy." + providerDomain + "." + providerService + ".admin")); + policies.add(policy); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain(domainName); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), privateKey)); + signedPolicies.setKeyId("0"); + + DomainData domain = new DomainData(); + domain.setName(domainName); + domain.setRoles(roles); + domain.setServices(services); + domain.setPolicies(signedPolicies); + + signedDomain.setDomain(domain); + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), privateKey)); + signedDomain.setKeyId("0"); + + return signedDomain; + } + + private SignedDomain createSignedDomainWildCard(String domainName, String tenantDomain) { + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName(generateRoleName(domainName, "superusers")); + List members = new ArrayList<>(); + members.add("user_domain.admin_user"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, "users")); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, "netops_superusers")); + role.setTrust(tenantDomain); + roles.add(role); + + List policies = new ArrayList<>(); + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + ":node.*"); + assertion.setAction("node_user"); + assertion.setRole(generateRoleName(domainName, "users")); + + List assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, "users")); + policies.add(policy); + + policy = new com.yahoo.athenz.zms.Policy(); + assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + ":node.*"); + assertion.setAction("node_sudo"); + assertion.setRole(generateRoleName(domainName, "netops_superusers")); + + assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, "netops_superusers")); + policies.add(policy); + + policy = new com.yahoo.athenz.zms.Policy(); + assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + ":node.*"); + assertion.setAction("node_user"); + assertion.setRole(generateRoleName(domainName, "superusers")); + + assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, "superusers")); + policies.add(policy); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain(domainName); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), privateKey)); + signedPolicies.setKeyId("0"); + + DomainData domain = new DomainData(); + domain.setName(domainName); + domain.setRoles(roles); + domain.setPolicies(signedPolicies); + domain.setModified(Timestamp.fromCurrentTime()); + + signedDomain.setDomain(domain); + + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), privateKey)); + signedDomain.setKeyId("0"); + + return signedDomain; + } + + private SignedDomain createTenantSignedDomainWildCard(String domainName, String providerDomain) { + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName(generateRoleName(domainName, "superusers")); + List members = new ArrayList<>(); + members.add("user_domain.siteops_user_1"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, "users")); + roles.add(role); + + List policies = new ArrayList<>(); + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource("*:role.netops_superusers"); + assertion.setAction("assume_role"); + assertion.setRole(generateRoleName(domainName, "superusers")); + + List assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, "netops_superusers")); + policies.add(policy); + + policy = new com.yahoo.athenz.zms.Policy(); + assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + "netops:node.*"); + assertion.setAction("node_user"); + assertion.setRole(generateRoleName(domainName, "users")); + + assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, "users")); + policies.add(policy); + + policy = new com.yahoo.athenz.zms.Policy(); + assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + "netops:node.*"); + assertion.setAction("node_sudo"); + assertion.setRole(generateRoleName(domainName, "superusers")); + + assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, "superusers")); + policies.add(policy); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain(domainName); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), privateKey)); + signedPolicies.setKeyId("0"); + + DomainData domain = new DomainData(); + domain.setName(domainName); + domain.setRoles(roles); + domain.setPolicies(signedPolicies); + + signedDomain.setDomain(domain); + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), privateKey)); + signedDomain.setKeyId("0"); + + return signedDomain; + } + + private SignedDomain createAwsSignedDomain(String domainName, String account) { + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName(generateRoleName(domainName, "admin")); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName(generateRoleName(domainName, "aws_role")); + members = new ArrayList<>(); + members.add("user_domain.user100"); + members.add("user_domain.user101"); + role.setMembers(members); + roles.add(role); + + List policies = new ArrayList<>(); + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + ":aws_role_name"); + assertion.setAction("assume_aws_role"); + assertion.setRole(generateRoleName(domainName, "aws_role")); + + List assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(generatePolicyName(domainName, "aws_policy")); + policies.add(policy); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain(domainName); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), privateKey)); + signedPolicies.setKeyId("0"); + + DomainData domain = new DomainData(); + domain.setName(domainName); + domain.setAccount(account); + domain.setRoles(roles); + domain.setPolicies(signedPolicies); + + signedDomain.setDomain(domain); + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), privateKey)); + signedDomain.setKeyId("0"); + + return signedDomain; + } + + private void setServicePublicKey(ServiceIdentity service, String id, String key) { + com.yahoo.athenz.zms.PublicKeyEntry keyEntry = new com.yahoo.athenz.zms.PublicKeyEntry(); + keyEntry.setId(id); + keyEntry.setKey(key); + List listKeys = new ArrayList<>(); + listKeys.add(keyEntry); + service.setPublicKeys(listKeys); + } + + private void setServicePublicKey(com.yahoo.athenz.zts.ServiceIdentity service, String id, String key) { + com.yahoo.athenz.zts.PublicKeyEntry keyEntry = new com.yahoo.athenz.zts.PublicKeyEntry(); + keyEntry.setId(id); + keyEntry.setKey(key); + List listKeys = new ArrayList<>(); + listKeys.add(keyEntry); + service.setPublicKeys(listKeys); + } + + @Test + public void testGetPublicKeyNotExistent() { + + String domain = "unknown"; + String service = "unknown"; + + String pubKey = zts.getPublicKey(domain, service, "0"); + assertNull(pubKey); + + pubKey = zts.getPublicKey(null, service, "0"); + assertNull(pubKey); + + pubKey = zts.getPublicKey(domain, null, "0"); + assertNull(pubKey); + } + + @Test + public void testGetPublicKey() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + String pubKey = zts.getPublicKey("coretech", "storage", "0"); + assertEquals(pubKey, ZTS_PEM_CERT0); + + pubKey = zts.getPublicKey("coretech", "storage", "100"); + assertNull(pubKey); + } + + @Test + public void testShouldRunDelegatedTrustCheckNullTrust() { + assertFalse(authorizer.shouldRunDelegatedTrustCheck(null, "TrustDomain")); + } + + @Test + public void testShouldRunDelegatedTrustCheckNullTrustDomain() { + assertTrue(authorizer.shouldRunDelegatedTrustCheck("TrustDomain", null)); + } + + @Test + public void testShouldRunDelegatedTrustCheckMatch() { + assertTrue(authorizer.shouldRunDelegatedTrustCheck("TrustDomain", "TrustDomain")); + } + + @Test + public void testShouldRunDelegatedTrustCheckNoMatch() { + assertFalse(authorizer.shouldRunDelegatedTrustCheck("TrustDomain1", "TrustDomain")); + } + + @Test + public void testEvaluateAccessNoAssertions() { + + DataCache domain = new DataCache(); + DomainData domainData = new DomainData(); + domainData.setName("coretech"); + domain.setDomainData(domainData); + domainData.setRoles(new ArrayList()); + Role role = new Role().setName("coretech:role.role1"); + domainData.getRoles().add(role); + Policy policy = new Policy().setName("coretech:policy.policy1"); + domainData.setPolicies(new com.yahoo.athenz.zms.SignedPolicies()); + domainData.getPolicies().setContents(new com.yahoo.athenz.zms.DomainPolicies()); + domainData.getPolicies().getContents().setPolicies(new ArrayList()); + domainData.getPolicies().getContents().getPolicies().add(policy); + assertEquals(authorizer.evaluateAccess(domain, null, null, null, null), AccessStatus.DENIED); + } + + @Test + public void testEvaluateAccessAssertionDeny() { + + DataCache domain = new DataCache(); + DomainData domainData = new DomainData(); + domainData.setName("coretech"); + domain.setDomainData(domainData); + domainData.setRoles(new ArrayList()); + Role role = createRoleObject("coretech", "role1", null, "user_domain.user1", null); + domainData.getRoles().add(role); + + Policy policy = new Policy().setName("coretech:policy.policy1"); + Assertion assertion = new Assertion(); + assertion.setAction("read"); + assertion.setEffect(AssertionEffect.DENY); + assertion.setResource("coretech:*"); + assertion.setRole("coretech:role.role1"); + policy.setAssertions(new ArrayList()); + policy.getAssertions().add(assertion); + domainData.setPolicies(new com.yahoo.athenz.zms.SignedPolicies()); + domainData.getPolicies().setContents(new com.yahoo.athenz.zms.DomainPolicies()); + domainData.getPolicies().getContents().setPolicies(new ArrayList()); + domainData.getPolicies().getContents().getPolicies().add(policy); + + assertEquals(authorizer.evaluateAccess(domain, "user_domain.user1", "read", "coretech:resource1", null), AccessStatus.DENIED); + } + + @Test + public void testEvaluateAccessAssertionAllow() { + + DataCache domain = new DataCache(); + DomainData domainData = new DomainData(); + domainData.setName("coretech"); + domain.setDomainData(domainData); + domainData.setRoles(new ArrayList()); + Role role = createRoleObject("coretech", "role1", null, "user_domain.user1", null); + domainData.getRoles().add(role); + + Policy policy = new Policy().setName("coretech:policy.policy1"); + Assertion assertion1 = new Assertion(); + assertion1.setAction("read"); + assertion1.setEffect(AssertionEffect.ALLOW); + assertion1.setResource("coretech:*"); + assertion1.setRole("coretech:role.role1"); + Assertion assertion2 = new Assertion(); + assertion2.setAction("read"); + assertion2.setEffect(AssertionEffect.ALLOW); + assertion2.setResource("coretech:resource1"); + assertion2.setRole("coretech:role.role1"); + policy.setAssertions(new ArrayList()); + policy.getAssertions().add(assertion1); + policy.getAssertions().add(assertion2); + domainData.setPolicies(new com.yahoo.athenz.zms.SignedPolicies()); + domainData.getPolicies().setContents(new com.yahoo.athenz.zms.DomainPolicies()); + domainData.getPolicies().getContents().setPolicies(new ArrayList()); + domainData.getPolicies().getContents().getPolicies().add(policy); + + assertEquals(authorizer.evaluateAccess(domain, "user_domain.user1", "read", "coretech:resource1", null), AccessStatus.ALLOWED); + } + + @Test + public void testGetHostServices() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + HostServices hosts = zts.getHostServices(null, "host1"); + assertTrue(hosts.getNames().size() == 1); + assertTrue(hosts.getNames().contains("coretech.storage")); + + hosts = zts.getHostServices(null, "host2"); + assertTrue(hosts.getNames().size() == 2); + assertTrue(hosts.getNames().contains("coretech.storage")); + assertTrue(hosts.getNames().contains("coretech.backup")); + + hosts = zts.getHostServices(null, "host3"); + assertTrue(hosts.getNames().size() == 1); + assertTrue(hosts.getNames().contains("coretech.backup")); + } + + @Test + public void testGetHostServicesInvalidHost() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + HostServices hosts = zts.getHostServices(null, "unknownHost"); + assertNull(hosts.getNames()); + } + + @Test + public void testGetPolicyList() { + + List policies = new ArrayList<>(); + List assertions = new ArrayList<>(); + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource("coretech:tenant.weather.*"); + assertion.setAction("read"); + assertion.setRole("coretech:role.readers"); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName("coretech:policy.reader"); + policies.add(policy); + + policy = new com.yahoo.athenz.zms.Policy(); + assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource("coretech:tenant.weather.*"); + assertion.setAction("write"); + assertion.setRole("coretech:role.writers"); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName("coretech:policy.writer"); + policies.add(policy); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain("coretech"); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), privateKey)); + signedPolicies.setKeyId("0"); + + DomainData domain = new DomainData(); + domain.setName("coretech"); + domain.setPolicies(signedPolicies); + domain.setModified(Timestamp.fromCurrentTime()); + + List policyList = zts.getPolicyList(domain); + assertEquals(policyList.size(), 2); + assertEquals(policyList.get(0).getName(), "coretech:policy.reader"); + assertEquals(policyList.get(1).getName(), "coretech:policy.writer"); + } + + @Test + public void testGetPolicyListPoliciesNull() { + + DomainData domain = new DomainData(); + domain.setName("coretech"); + domain.setPolicies(null); + domain.setModified(Timestamp.fromCurrentTime()); + + List policyList = zts.getPolicyList(domain); + assertEquals(policyList.size(), 0); + } + + @Test + public void testGetPolicyListPoliciesEmpty() { + + DomainData domain = new DomainData(); + domain.setName("coretech"); + + List policies = new ArrayList<>(); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain("coretech"); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), privateKey)); + signedPolicies.setKeyId("0"); + + domain.setPolicies(signedPolicies); + domain.setModified(Timestamp.fromCurrentTime()); + + List policyList = zts.getPolicyList(domain); + assertEquals(policyList.size(), 0); + } + + @Test + public void testGetRoleToken() { + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + RoleToken roleToken = zts.getRoleToken(context, "coretech", null, Integer.valueOf(600), + Integer.valueOf(1200), null); + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("writers")); + assertTrue(roleToken.getToken().contains(";h=localhost;")); + assertTrue(roleToken.getToken().contains(";i=10.11.12.13")); + assertTrue(roleToken.getToken().contains(";p=user_domain.user;")); + assertEquals(roleToken.getExpiryTime(), token.getExpiryTime()); + + Principal principal1 = SimplePrincipal.create("user_domain", "user1", + "v=U1;d=user_domain;n=user1;s=signature", 0, null); + ResourceContext context1 = createResourceContext(principal1); + + roleToken = zts.getRoleToken(context1, "coretech", null, null, Integer.valueOf(1200), null); + token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 2); + assertTrue(token.getRoles().contains("readers")); + assertTrue(token.getRoles().contains("writers")); + + Principal principal4 = SimplePrincipal.create("user_domain", "user4", + "v=U1;d=user_domain;n=user4;s=signature", 0, null); + ResourceContext context4 = createResourceContext(principal4); + + roleToken = zts.getRoleToken(context4, "coretech", null, Integer.valueOf(600), + null, null); + token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("readers")); + assertTrue(roleToken.getToken().contains(";p=user_domain.user4;")); + } + + @Test + public void testGetRoleTokenEmptyArguments() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + RoleToken roleToken = zts.getRoleToken(context, "coretech", "", null, null, null); + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("writers")); + } + + @Test + public void testGetRoleTokenNoRoleMatch() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "invalidUser", + "v=U1;d=user_domain;n=invalidUuser;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + try { + zts.getRoleToken(context, "coretech", null, Integer.valueOf(600), + Integer.valueOf(1200), null); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + } + } + + @Test + public void testGetRoleTokenInvalidDomain() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + try { + zts.getRoleToken(context, "invalidDomain", null, Integer.valueOf(600), + Integer.valueOf(1200), null); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testGetRoleTokenSpecifiedRoleValid() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + RoleToken roleToken = zts.getRoleToken(context, "coretech", "writers", Integer.valueOf(600), + Integer.valueOf(1200), null); + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("writers")); + + Principal principal1 = SimplePrincipal.create("user_domain", "user1", + "v=U1;d=user_domain;n=user1;s=signature", 0, null); + ResourceContext context1 = createResourceContext(principal1); + + roleToken = zts.getRoleToken(context1, "coretech", "writers", null, Integer.valueOf(1200), null); + token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("writers")); + + Principal principal4 = SimplePrincipal.create("user_domain", "user4", + "v=U1;d=user_domain;n=user4;s=signature", 0, null); + ResourceContext context4 = createResourceContext(principal4); + + roleToken = zts.getRoleToken(context4, "coretech", "readers", Integer.valueOf(600), null, null); + token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("readers")); + } + + @Test + public void testGetRoleTokenSpecifiedRoleInValid() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + try { + zts.getRoleToken(context, "coretech", "coretech:role.readers", Integer.valueOf(600), + Integer.valueOf(1200), null); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testGetRoleTokenSpecifiedRoleNoMatch() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + try { + zts.getRoleToken(context, "coretech", "updaters", Integer.valueOf(600), + Integer.valueOf(1200), null); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + } + } + + @Test + public void testGetRoleTokenTrustDomainWildCard() { + + SignedDomain signedDomain = createSignedDomainWildCard("weather", "netops"); + store.processDomain(signedDomain, false); + + signedDomain = createTenantSignedDomainWildCard("netops", "weather"); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "siteops_user_1", + "v=U1;d=user_domain;n=siteops_user_1;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + RoleToken roleToken = zts.getRoleToken(context, "weather", null, null, null, null); + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("netops_superusers")); + } + + @Test + public void testGetRoleTokenTrustDomainWildCardGivenRole() { + + SignedDomain signedDomain = createSignedDomainWildCard("weather", "netops"); + store.processDomain(signedDomain, false); + + signedDomain = createTenantSignedDomainWildCard("netops", "weather"); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "siteops_user_1", + "v=U1;d=user_domain;n=siteops_user_1;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + RoleToken roleToken = zts.getRoleToken(context, "weather", "netops_superusers", null, null, null); + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("netops_superusers")); + } + + @Test + public void testGetRoleAccess() { + SignedDomain signedDomain = createSignedDomain("roleaccess", "tenantrole", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", "v=U1;d=user_domain;n=user;s=sig", 0, null); + ResourceContext context = createResourceContext(principal); + + RoleAccess roleAccess = zts.getRoleAccess(context, "roleaccess", "user_domain.user"); + assertEquals(roleAccess.getRoles().size(), 1); + assertTrue(roleAccess.getRoles().contains("writers")); + + Principal principal1 = SimplePrincipal.create("user_domain", "user1", "v=U1;d=user_domain;n=user1;s=sig", 0, null); + ResourceContext context1 = createResourceContext(principal1); + + roleAccess = zts.getRoleAccess(context1, "roleaccess", "user_domain.user1"); + assertEquals(roleAccess.getRoles().size(), 2); + assertTrue(roleAccess.getRoles().contains("readers")); + assertTrue(roleAccess.getRoles().contains("writers")); + + Principal principal4 = SimplePrincipal.create("user_domain", "user4", "v=U1;d=user_domain;n=user1;s=sig", 0, null); + ResourceContext context4 = createResourceContext(principal4); + + roleAccess = zts.getRoleAccess(context4, "roleaccess", "user_domain.user4"); + assertEquals(roleAccess.getRoles().size(), 1); + assertTrue(roleAccess.getRoles().contains("readers")); + } + + @Test + public void testGetRoleTokenUnauthorizedProxy() { + SignedDomain signedDomain = createSignedDomain("coretech-proxy1", "weather-proxy1", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", "v=U1;d=user_domain;n=user;s=sig", 0, null); + ResourceContext context = createResourceContext(principal); + + try { + zts.getRoleToken(context, "coretech-proxy1", null, Integer.valueOf(600), + Integer.valueOf(1200), "user_domain.unknown-proxy-user"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + assertTrue(ex.getMessage().contains("not authorized for proxy role token request")); + } + } + + @Test + public void testGetRoleTokenProxyUser() { + + List writers = new ArrayList<>(); + writers.add("user_domain.proxy-user1"); + writers.add("user_domain.joe"); + + List readers = new ArrayList<>(); + readers.add("user_domain.proxy-user2"); + readers.add("user_domain.jane"); + + SignedDomain signedDomain = createSignedDomain("coretech-proxy2", "weather-proxy2", "storage", + writers, readers, true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "proxy-user1", + "v=U1;d=user_domain;n=proxy-user1;s=sig", 0, null); + ResourceContext context = createResourceContext(principal); + + RoleToken roleToken = zts.getRoleToken(context, "coretech-proxy2", null, Integer.valueOf(600), + Integer.valueOf(1200), "user_domain.joe"); + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("writers")); + assertTrue(roleToken.getToken().contains(";h=localhost;")); + assertTrue(roleToken.getToken().contains(";i=10.11.12.13")); + assertTrue(roleToken.getToken().contains(";p=user_domain.joe;")); + assertTrue(roleToken.getToken().contains(";proxy=user_domain.proxy-user1;")); + assertTrue(roleToken.getToken().contains(";c=1;")); + assertEquals(roleToken.getExpiryTime(), token.getExpiryTime()); + + principal = SimplePrincipal.create("user_domain", "proxy-user2", + "v=U1;d=user_domain;n=proxy-user2;s=sig", 0, null); + context = createResourceContext(principal); + + roleToken = zts.getRoleToken(context, "coretech-proxy2", null, Integer.valueOf(600), + Integer.valueOf(1200), "user_domain.jane"); + token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("readers")); + assertTrue(roleToken.getToken().contains(";h=localhost;")); + assertTrue(roleToken.getToken().contains(";i=10.11.12.13")); + assertTrue(roleToken.getToken().contains(";p=user_domain.jane;")); + assertTrue(roleToken.getToken().contains(";proxy=user_domain.proxy-user2;")); + assertTrue(roleToken.getToken().contains(";c=1;")); + assertEquals(roleToken.getExpiryTime(), token.getExpiryTime()); + } + + @Test + public void testGetRoleTokenProxyUserMismatchRoles() { + + List writers = new ArrayList<>(); + writers.add("user_domain.proxy-user1"); + writers.add("user_domain.joe"); + + List readers = new ArrayList<>(); + readers.add("user_domain.proxy-user2"); + readers.add("user_domain.jane"); + readers.add("user_domain.proxy-user1"); + + SignedDomain signedDomain = createSignedDomain("coretech-proxy3", "weather-proxy3", "storage", + writers, readers, true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "proxy-user1", + "v=U1;d=user_domain;n=proxy-user1;s=sig", 0, null); + ResourceContext context = createResourceContext(principal); + + try { + zts.getRoleToken(context, "coretech-proxy3", null, Integer.valueOf(600), + Integer.valueOf(1200), "user_domain.joe"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + assertTrue(ex.getMessage().contains("does not have access to the same set of roles as proxy")); + } + } + + @Test + public void testGetRoleTokenProxyUserSpecificRole() { + + List writers = new ArrayList<>(); + writers.add("user_domain.proxy-user1"); + writers.add("user_domain.joe"); + + List readers = new ArrayList<>(); + readers.add("user_domain.proxy-user2"); + readers.add("user_domain.jane"); + readers.add("user_domain.proxy-user1"); + + SignedDomain signedDomain = createSignedDomain("coretech-proxy4", "weather-proxy4", "storage", + writers, readers, true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "proxy-user1", + "v=U1;d=user_domain;n=proxy-user1;s=sig", 0, null); + ResourceContext context = createResourceContext(principal); + + RoleToken roleToken = zts.getRoleToken(context, "coretech-proxy4", "writers", Integer.valueOf(600), + Integer.valueOf(1200), "user_domain.joe"); + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertEquals(token.getRoles().size(), 1); + assertTrue(token.getRoles().contains("writers")); + assertTrue(roleToken.getToken().contains(";h=localhost;")); + assertTrue(roleToken.getToken().contains(";i=10.11.12.13")); + assertTrue(roleToken.getToken().contains(";p=user_domain.joe;")); + assertTrue(roleToken.getToken().contains(";proxy=user_domain.proxy-user1;")); + assertEquals(roleToken.getExpiryTime(), token.getExpiryTime()); + } + + @Test + public void testLookupServiceIdentity() { + + List services = new ArrayList<>(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName(generateServiceIdentityName("coretech", "storage")); + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + services.add(service); + + service = new ServiceIdentity(); + service.setName(generateServiceIdentityName("coretech", "backup")); + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + services.add(service); + + DomainData domain = new DomainData(); + domain.setName("coretech"); + domain.setServices(services); + + com.yahoo.athenz.zts.ServiceIdentity svc = zts.lookupServiceIdentity(domain, "coretech.storage"); + assertNotNull(svc); + } + + @Test + public void testLookupServiceIdentityNull() { + DomainData domain = new DomainData(); + domain.setName("coretech"); + domain.setServices(null); + + com.yahoo.athenz.zts.ServiceIdentity svc = zts.lookupServiceIdentity(domain, "coretech.storage"); + assertNull(svc); + } + + @Test + public void testLookupServiceIdentityNoMatch() { + List services = new ArrayList<>(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName(generateServiceIdentityName("coretech", "storage")); + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + services.add(service); + + service = new ServiceIdentity(); + service.setName(generateServiceIdentityName("coretech", "backup")); + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + services.add(service); + + DomainData domain = new DomainData(); + domain.setName("coretech"); + domain.setServices(services); + + com.yahoo.athenz.zts.ServiceIdentity svc = zts.lookupServiceIdentity(domain, "coretech.sync"); + assertNull(svc); + } + + @Test + public void testGetPublicKeyEntry() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + PublicKeyEntry entry = zts.getPublicKeyEntry(null, "coretech", "storage", "0"); + assertEquals(entry.getId(), "0"); + assertEquals(entry.getKey(), ZTS_Y64_CERT0); + } + + @Test + public void testGetPublicKeyEntryInvalidKeyId() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + // with null we get 400 + try { + zts.getPublicKeyEntry(null, "coretech", "storage", null); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + // with nonexistent we get 404 + try { + zts.getPublicKeyEntry(null, "coretech", "storage", "999999"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testGetPublicKeyEntryInvalidDomain() { + + try { + zts.getPublicKeyEntry(null, "nonexistentdomain", "storage", "0"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testGetPublicKeyEntryInvalidService() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + try { + zts.getPublicKeyEntry(null, "coretech", "nonexistentservice", "0"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testGetServiceIdentity() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + com.yahoo.athenz.zts.ServiceIdentity svc = zts.getServiceIdentity(null, "coretech", "storage"); + assertNotNull(svc); + assertEquals(svc.getName(), "coretech.storage"); + + svc = zts.getServiceIdentity(null, "coretech", "backup"); + assertNotNull(svc); + assertEquals(svc.getName(), "coretech.backup"); + } + + @Test + public void testGetServiceIdentityInvalid() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + try { + @SuppressWarnings("unused") + com.yahoo.athenz.zts.ServiceIdentity svc = zts.getServiceIdentity(null, "coretech", "storage2"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + + try { + @SuppressWarnings("unused") + com.yahoo.athenz.zts.ServiceIdentity svc = zts.getServiceIdentity(null, "testDomain2", "storage"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testAddServiceNameToListValid() { + + List names = new ArrayList<>(); + zts.addServiceNameToList("coretech.storage", "coretech.", names); + + assertEquals(names.size(), 1); + assertTrue(names.contains("storage")); + } + + @Test + public void testAddServiceNameToListInValid() { + + List names = new ArrayList<>(); + zts.addServiceNameToList("coretech.storage", "weather.", names); + + assertEquals(names.size(), 0); + } + + @Test + public void testGetServiceIdentityList() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + com.yahoo.athenz.zts.ServiceIdentityList svcList = zts.getServiceIdentityList(null, "coretech"); + assertEquals(svcList.getNames().size(), 2); + assertTrue(svcList.getNames().contains("storage")); + assertTrue(svcList.getNames().contains("backup")); + } + + @Test + public void testGetServiceIdentityListInvalidDomain() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + try { + @SuppressWarnings("unused") + com.yahoo.athenz.zts.ServiceIdentityList svcList = zts.getServiceIdentityList(null, "testDomain2"); + fail(); + } catch (ResourceException ex) { + assertTrue(true); + } + } + + @Test + public void testGetServiceIdentityListNoServices() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", false); + store.processDomain(signedDomain, false); + + com.yahoo.athenz.zts.ServiceIdentityList svcList = zts.getServiceIdentityList(null, "coretech"); + assertEquals(svcList.getNames().size(), 0); + } + + @Test + public void testValidate() { + com.yahoo.athenz.zts.ServiceIdentity service = new com.yahoo.athenz.zts.ServiceIdentity(); + service.setName(generateServiceIdentityName("coretech", "storage")); + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + zts.validate(service, "ServiceIdentity", "testValidate"); + assertTrue(true); + } + + @Test + public void testValidateObjNull() { + try { + zts.validate(null, "SignedDomain", "testValidate"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testValidateObjInvalid() { + com.yahoo.athenz.zts.ServiceIdentity service = new com.yahoo.athenz.zts.ServiceIdentity(); + service.setName(generateServiceIdentityName("coretech", "storage")); + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + try { + zts.validate(service, "Policy", "testValidate"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testConvertEmptyStringToNullStringNull() { + assertNull(zts.convertEmptyStringToNull(null)); + } + + @Test + public void testConvertEmptyStringToNullStringEmpty() { + assertNull(zts.convertEmptyStringToNull("")); + } + + @Test + public void testConvertEmptyStringToNullStringNotEmpty() { + assertEquals(zts.convertEmptyStringToNull("test"), "test"); + } + + @Test + public void testEmitMonmetricError() { + int errorCode = 403; + String caller = "forbiddenError"; + boolean isEmitMonmetricError; + com.yahoo.athenz.common.metrics.Metric metric = getMetric(); + // negative tests + isEmitMonmetricError = ZTSUtils.emitMonmetricError(errorCode, null, ZTSConsts.ZTS_UNKNOWN_DOMAIN, metric); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZTSUtils.emitMonmetricError(errorCode, "", ZTSConsts.ZTS_UNKNOWN_DOMAIN, metric); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZTSUtils.emitMonmetricError(errorCode, new String(), null, metric); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZTSUtils.emitMonmetricError(errorCode, "invalidcharacterslike...$!?", null, metric); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZTSUtils.emitMonmetricError(errorCode, "spaces are not allowed", ZTSConsts.ZTS_UNKNOWN_DOMAIN, metric); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZTSUtils.emitMonmetricError(0, caller, null, metric); + assertFalse(isEmitMonmetricError); + + isEmitMonmetricError = ZTSUtils.emitMonmetricError(-100, caller, null, metric); + assertFalse(isEmitMonmetricError); + + // positive tests + isEmitMonmetricError = ZTSUtils.emitMonmetricError(errorCode, caller, null, metric); + assertTrue(isEmitMonmetricError); + + isEmitMonmetricError = ZTSUtils.emitMonmetricError(errorCode, " " + caller + " ", null, metric); + assertTrue(isEmitMonmetricError); + } + + @Test + public void testDetermineTokenTimeoutBothNull() { + assertEquals(zts.determineTokenTimeout(null, null), roleTokenDefaultTimeout); + } + + @Test + public void testDetermineTokenTimeoutMinNull() { + assertEquals(zts.determineTokenTimeout(null, Integer.valueOf(100)), 100); + } + + @Test + public void testDetermineTokenTimeoutMaxNull() { + assertEquals(zts.determineTokenTimeout(Integer.valueOf(100), null), roleTokenDefaultTimeout); + } + + @Test + public void testDetermineTokenTimeoutMinInvalid() { + assertEquals(zts.determineTokenTimeout(Integer.valueOf(-10), null), roleTokenDefaultTimeout); + } + + @Test + public void testDetermineTokenTimeoutMaxInvalid() { + assertEquals(zts.determineTokenTimeout(null, Integer.valueOf(-10)), roleTokenDefaultTimeout); + } + + @Test + public void testDetermineTokenTimeoutDefaultBigger() { + assertEquals(zts.determineTokenTimeout(Integer.valueOf(3200), null), 3200); + } + + @Test + public void testDetermineTokeTimeoutDefaultSmaller() { + assertEquals(zts.determineTokenTimeout(Integer.valueOf(1200), null), roleTokenDefaultTimeout); + } + + @Test + public void testDetermineTokeTimeoutMaxValueMaxExceeded() { + assertEquals(zts.determineTokenTimeout(null, Integer.valueOf(120000)), roleTokenMaxTimeout); + } + + @Test + public void testDetermineTokeTimeoutMinValueMaxExceeded() { + assertEquals(zts.determineTokenTimeout(Integer.valueOf(120000), null), roleTokenMaxTimeout); + } + + @Test + public void testRoleTokenAddrNoLoopbackAuditLog() { + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(servletRequest.getRemoteAddr()).thenReturn("10.10.10.11"); + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + + ChangeLogStore structStore = new ZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + privateKey, "0"); + + Metric debugMetric = new com.yahoo.athenz.common.metrics.impl.NoOpMetric(); + DataStore store = new DataStore(structStore, null); + zts = new ZTSImpl("localhost", store, null, null, debugMetric, privateKey, "0", alogger, null); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal, servletRequest); + + RoleToken roleToken = zts.getRoleToken(context, "coretech", null, Integer.valueOf(600), + Integer.valueOf(1200), null); + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertNotNull(token); + String unsignToken = token.getUnsignedToken(); + for (String msg: aLogMsgs) { + assertTrue(msg.contains("SUCCESS ROLETOKEN=(" + unsignToken)); + assertTrue(msg.contains("CLIENT-IP=(10.10.10.11)")); + break; + } + } + + @Test + public void testGetRoleTokenAddrLoopbackNoXFFAuditLog() { + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(servletRequest.getRemoteAddr()).thenReturn("127.0.0.1"); + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + + ChangeLogStore structStore = new ZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + privateKey, "0"); + + Metric debugMetric = new com.yahoo.athenz.common.metrics.impl.NoOpMetric(); + DataStore store = new DataStore(structStore, null); + zts = new ZTSImpl("localhost", store, null, null, debugMetric, privateKey, "0", alogger, null); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal, servletRequest); + + RoleToken roleToken = zts.getRoleToken(context, "coretech", null, Integer.valueOf(600), + Integer.valueOf(1200), null); + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertNotNull(token); + String unsignToken = token.getUnsignedToken(); + for (String msg: aLogMsgs) { + assertTrue(msg.contains("SUCCESS ROLETOKEN=(" + unsignToken)); + assertTrue(msg.contains("i=127.0.0.1")); + assertTrue(msg.contains("CLIENT-IP=(127.0.0.1)")); + break; + } + } + + @Test + public void testGetRoleTokenAddrLoopbackXFFSingeValueAuditLog() { + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(servletRequest.getRemoteAddr()).thenReturn("127.0.0.1"); + Mockito.when(servletRequest.getHeader("X-Forwarded-For")).thenReturn("10.10.10.12"); + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + + ChangeLogStore structStore = new ZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + privateKey, "0"); + + Metric debugMetric = new com.yahoo.athenz.common.metrics.impl.NoOpMetric(); + DataStore store = new DataStore(structStore, null); + zts = new ZTSImpl("localhost", store, null, null, debugMetric, privateKey, "0", alogger, null); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal, servletRequest); + + RoleToken roleToken = zts.getRoleToken(context, "coretech", null, Integer.valueOf(600), + Integer.valueOf(1200), null); + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertNotNull(token); + String unsignToken = token.getUnsignedToken(); + for (String msg: aLogMsgs) { + assertTrue(msg.contains("SUCCESS ROLETOKEN=(" + unsignToken)); + assertTrue(msg.contains("CLIENT-IP=(10.10.10.12)")); + break; + } + } + + @Test + public void testGetRoleTokenAddrLoopbackXFFMultipleValuesAuditLog() { + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(servletRequest.getRemoteAddr()).thenReturn("127.0.0.1"); + Mockito.when(servletRequest.getHeader("X-Forwarded-For")).thenReturn("10.10.10.11, 10.11.11.11, 10.12.12.12"); + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + + ChangeLogStore structStore = new ZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + privateKey, "0"); + + Metric debugMetric = new com.yahoo.athenz.common.metrics.impl.NoOpMetric(); + DataStore store = new DataStore(structStore, null); + zts = new ZTSImpl("localhost", store, null, null, debugMetric, privateKey, "0", alogger, null); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal, servletRequest); + + RoleToken roleToken = zts.getRoleToken(context, "coretech", null, Integer.valueOf(600), + Integer.valueOf(1200), null); + com.yahoo.athenz.auth.token.RoleToken token = new com.yahoo.athenz.auth.token.RoleToken(roleToken.getToken()); + assertNotNull(token); + String unsignToken = token.getUnsignedToken(); + for (String msg: aLogMsgs) { + assertTrue(msg.contains("SUCCESS ROLETOKEN=(" + unsignToken)); + assertTrue(msg.contains("CLIENT-IP=(10.12.12.12)")); + break; + } + } + + @Test + public void testGetRoleTokenNoRoleMatchAuditLog() { + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(servletRequest.getRemoteAddr()).thenReturn("99.88.77.66"); + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + + ChangeLogStore structStore = new ZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + privateKey, "0"); + + Metric debugMetric = new com.yahoo.athenz.common.metrics.impl.NoOpMetric(); + DataStore store = new DataStore(structStore, null); + zts = new ZTSImpl("localhost", store, null, null, debugMetric, privateKey, "0", alogger, null); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "invalidUser", + "v=U1;d=user_domain;n=invalidUser;s=signature", 0, null); + ResourceContext context = createResourceContext(principal, servletRequest); + + try { + zts.getRoleToken(context, "coretech", null, Integer.valueOf(600), + Integer.valueOf(1200), null); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + } + + for (String msg: aLogMsgs) { + assertTrue(msg.contains("ERROR=(Principal Has No Access to Domain)")); + assertTrue(msg.contains("CLIENT-IP=(99.88.77.66)")); + assertTrue(msg.contains("WHO=(who-name=invalidUser,who-domain=user_domain,who-yrn=user_domain.invalidUser)")); + break; + } + } + + @Test + public void testGetRoleTokenInvalidDomainAuditLog() { + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(servletRequest.getRemoteAddr()).thenReturn("55.88.77.66"); + + final java.util.Set aLogMsgs = new java.util.HashSet(); + AuditLogger alogger = new AuditLogger() { + public void log(String logMsg, String msgVersionTag) { + aLogMsgs.add(logMsg); + } + public void log(AuditLogMsgBuilder msgBldr) { + String msg = msgBldr.build(); + aLogMsgs.add(msg); + } + }; + + ChangeLogStore structStore = new ZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + privateKey, "0"); + + Metric debugMetric = new com.yahoo.athenz.common.metrics.impl.NoOpMetric(); + DataStore store = new DataStore(structStore, null); + zts = new ZTSImpl("localhost", store, null, null, debugMetric, privateKey, "0", alogger, null); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal, servletRequest); + + try { + zts.getRoleToken(context, "invalidDomain", null, Integer.valueOf(600), + Integer.valueOf(1200), null); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + + for (String msg: aLogMsgs) { + assertTrue(msg.contains("ERROR=(No Such Domain)")); + assertTrue(msg.contains("CLIENT-IP=(55.88.77.66)")); + assertTrue(msg.contains("WHO=(who-name=user,who-domain=user_domain,who-yrn=user_domain.user)")); + break; + } + } + + @Test + public void testRetrieveTenantDomainNameInvalidEntries() { + + // less than 4 components + + assertNull(zts.retrieveTenantDomainName("dom1", null)); + assertNull(zts.retrieveTenantDomainName("dom1.tenant", null)); + assertNull(zts.retrieveTenantDomainName("dom1.tenant.dom3", null)); + + // second component is not tenant + + assertNull(zts.retrieveTenantDomainName("dom1.dom2.dom3.admin", null)); + assertNull(zts.retrieveTenantDomainName("dom1.dom2.tenant.read", null)); + assertNull(zts.retrieveTenantDomainName("tenant.dom2.dom3.write", null)); + + // service name does not match to the given value + + assertNull(zts.retrieveTenantDomainName("service1.tenant.dom3.read", "service2")); + assertNull(zts.retrieveTenantDomainName("service2.tenant.dom3.dom4.admin", "service")); + } + + @Test + public void testRetrieveTenantDomainName4CompsValidDomain() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + assertEquals("coretech", zts.retrieveTenantDomainName("storage.tenant.coretech.admin", "storage")); + assertEquals("coretech", zts.retrieveTenantDomainName("storage.tenant.coretech.admin", null)); + } + + @Test + public void testRetrieveTenantDomainName4CompsInvalidDomain() { + + assertNull(zts.retrieveTenantDomainName("storage.tenant.coretech_unknown.admin", "storage")); + assertNull(zts.retrieveTenantDomainName("storage.tenant.coretech_unknown.admin", null)); + } + + @Test + public void testRetrieveTenantDomainName4PlusCompsValidDomainWithResourceGroup() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + assertEquals("coretech", zts.retrieveTenantDomainName("storage.tenant.coretech.resource_group.admin", "storage")); + assertEquals("coretech", zts.retrieveTenantDomainName("storage.tenant.coretech.resource_group.admin", null)); + + signedDomain = createSignedDomain("coretech.office.burbank", "weather", "storage", true); + store.processDomain(signedDomain, false); + + assertEquals("coretech.office.burbank", zts.retrieveTenantDomainName("storage.tenant.coretech.office.burbank.resource_group.admin", "storage")); + assertEquals("coretech.office.burbank", zts.retrieveTenantDomainName("storage.tenant.coretech.office.burbank.resource_group.admin", null)); + } + + + @Test + public void testRetrieveTenantDomainName4PlusCompsValidDomainWithOutResourceGroup() { + + SignedDomain signedDomain = createSignedDomain("coretech.office.burbank", "weather", "storage", true); + store.processDomain(signedDomain, false); + + assertEquals("coretech.office.burbank", zts.retrieveTenantDomainName("storage.tenant.coretech.office.burbank.admin", "storage")); + assertEquals("coretech.office.burbank", zts.retrieveTenantDomainName("storage.tenant.coretech.office.burbank.admin", null)); + } + + @Test + public void testRetrieveTenantDomainName4PlusCompsInvalidDomain() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + assertNull(zts.retrieveTenantDomainName("storage.tenant.coretech.office.glendale.admin", "storage")); + assertNull(zts.retrieveTenantDomainName("storage.tenant.coretech.office.glendale.resource_group.admin", null)); + } + + @Test + public void testGetTenantDomainsSingleDomainWithUserDomain() { + + SignedDomain signedDomain = createSignedDomain("athenz.product", "weather.frontpage", "storage", true); + store.processDomain(signedDomain, false); + + signedDomain = createTenantSignedDomain("weather.frontpage", "athenz.product", "storage"); + store.processDomain(signedDomain, false); + + TenantDomains tenantDomains = zts.getTenantDomains(null, "athenz.product", "user_domain.user100", null, null); + assertNotNull(tenantDomains); + assertEquals(tenantDomains.getTenantDomainNames().size(), 1); + assertEquals(tenantDomains.getTenantDomainNames().get(0), "weather.frontpage"); + } + + @Test + public void testGetTenantDomainsSingleDomain() { + + SignedDomain signedDomain = createSignedDomain("athenz.product", "weather.frontpage", "storage", true); + store.processDomain(signedDomain, false); + + signedDomain = createTenantSignedDomain("weather.frontpage", "athenz.product", "storage"); + store.processDomain(signedDomain, false); + + TenantDomains tenantDomains = zts.getTenantDomains(null, "athenz.product", "user_domain.user100", null, null); + assertNotNull(tenantDomains); + assertEquals(tenantDomains.getTenantDomainNames().size(), 1); + assertEquals(tenantDomains.getTenantDomainNames().get(0), "weather.frontpage"); + } + + @Test + public void testGetTenantDomainsMultipleDomains() { + + SignedDomain signedDomain = createMultipleSignedDomains("athenz.multiple", "hockey.kings", "hockey.stars", + "storage", true); + store.processDomain(signedDomain, false); + + signedDomain = createTenantSignedDomain("hockey.kings", "athenz.multiple", "storage"); + store.processDomain(signedDomain, false); + + signedDomain = createTenantSignedDomain("hockey.stars", "athenz.multiple", "storage"); + store.processDomain(signedDomain, false); + + TenantDomains tenantDomains = zts.getTenantDomains(null, "athenz.multiple", "user_domain.user100", null, null); + assertNotNull(tenantDomains); + assertEquals(tenantDomains.getTenantDomainNames().size(), 2); + assertTrue(tenantDomains.getTenantDomainNames().contains("hockey.kings")); + assertTrue(tenantDomains.getTenantDomainNames().contains("hockey.stars")); + } + + @Test + public void testGetTenantDomainsInvalidUser() { + + SignedDomain signedDomain = createSignedDomain("athenz.product", "weather.frontpage", "storage", true); + store.processDomain(signedDomain, false); + + signedDomain = createTenantSignedDomain("weather.frontpage", "athenz.product", "storage"); + store.processDomain(signedDomain, false); + + TenantDomains tenantDomains = zts.getTenantDomains(null, "athenz.product", "user1099", null, null); + assertNotNull(tenantDomains); + assertEquals(tenantDomains.getTenantDomainNames().size(), 0); + } + + @Test + public void testGetTenantDomainsInvalidDomain() { + + SignedDomain signedDomain = createSignedDomain("athenz.product", "weather.frontpage", "storage", true); + store.processDomain(signedDomain, false); + + signedDomain = createTenantSignedDomain("weather.frontpage", "athenz.product", "storage"); + store.processDomain(signedDomain, false); + + try { + zts.getTenantDomains(null, "athenz.non_product", "user100", null, null); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testResourceContext() { + + RsrcCtxWrapper ctx = (RsrcCtxWrapper) zts.newResourceContext(mockServletRequest, mockServletResponse); + assertNotNull(ctx); + assertNotNull(ctx.context()); + assertNull(ctx.principal()); + assertEquals(ctx.request(), mockServletRequest); + assertEquals(ctx.response(), mockServletResponse); + + // throw exception without struct + try { + com.yahoo.athenz.common.server.rest.ResourceException restExc = new com.yahoo.athenz.common.server.rest.ResourceException(401, "failed message"); + ctx.throwZtsException(restExc); + fail(); + } catch (ResourceException ex) { + assertEquals(401, ex.getCode()); + assertEquals( ((ResourceError) ex.data).message, "failed message"); + } + } + + @Test + public void testVerifyAWSAssumeRoleInvalidDomain() { + assertFalse(zts.verifyAWSAssumeRole("unknown-domain", "role", "user_domain.user")); + } + + @Test + public void testVerifyAWSAssumeRoleNoRoles() { + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "1234"); + store.processDomain(signedDomain, false); + assertFalse(zts.verifyAWSAssumeRole("athenz.product", "role", "user_domain.user200")); + } + + @Test + public void testVerifyAWSAssumeRole() { + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "1234"); + store.processDomain(signedDomain, false); + + // our group includes user100 and user101 + assertTrue(zts.verifyAWSAssumeRole("athenz.product", "athenz.product:aws_role_name", "user_domain.user100")); + assertTrue(zts.verifyAWSAssumeRole("athenz.product", "athenz.product:aws_role_name", "user_domain.user101")); + assertFalse(zts.verifyAWSAssumeRole("athenz.product", "athenz.product:aws_role_name", "user_domain.user102")); + } + + @Test + public void testVerifyAWSAssumeRoleNoResourceMatch() { + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "1234"); + store.processDomain(signedDomain, false); + + assertFalse(zts.verifyAWSAssumeRole("athenz.product", "athenz.product:aws2_role_name", "user_domain.user100")); + assertFalse(zts.verifyAWSAssumeRole("athenz.product", "athenz.product:aws2_role_name", "user_domain.user101")); + assertFalse(zts.verifyAWSAssumeRole("athenz.product", "athenz.product:aws2_role_name", "user_domain.user102")); + } + + @Test + public void testGetAWSTemporaryCredentialsNoCloudStore() { + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "1234"); + store.processDomain(signedDomain, false); + + try { + zts.getAWSTemporaryCredentials(null, "athenz.product", "aws_role_name"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testGetAWSTemporaryCredentialsForbidden() { + + Principal principal = SimplePrincipal.create("user_domain", "user102", + "v=U1;d=user_domain;n=user102;s=signature", 0, null); + CloudStore cloudStore = Mockito.mock(CloudStore.class); + Mockito.when(cloudStore.isAwsEnabled()).thenReturn(true); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "1234"); + store.processDomain(signedDomain, false); + + try { + zts.getAWSTemporaryCredentials(createResourceContext(principal), "athenz.product", "aws_role_name"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + } + } + + @Test + public void testGetAWSTemporaryCredentialsNoAwsAccount() { + + Principal principal = SimplePrincipal.create("user_domain", "user101", + "v=U1;d=user_domain;n=user101;s=signature", 0, null); + CloudStore cloudStore = new MockCloudStore(); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", null); + store.processDomain(signedDomain, false); + + try { + zts.getAWSTemporaryCredentials(createResourceContext(principal), "athenz.product", "aws_role_name"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testGetAWSTemporaryCredentials() { + + Principal principal = SimplePrincipal.create("user_domain", "user101", + "v=U1;d=user_domain;n=user101;s=signature", 0, null); + CloudStore cloudStore = new MockCloudStore(); + ((MockCloudStore) cloudStore).setMockFields("1234", "aws_role_name", "user_domain.user101"); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "1234"); + store.processDomain(signedDomain, false); + + AWSTemporaryCredentials creds = zts.getAWSTemporaryCredentials( + createResourceContext(principal), "athenz.product", "aws_role_name"); + assertNotNull(creds); + + // now try a failure case + + try { + ((MockCloudStore) cloudStore).setMockFields("1234", "aws_role2_name", "user_domain.user101"); + zts.getAWSTemporaryCredentials(createResourceContext(principal), "athenz.product", "aws_role_name"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testRetrieveResourceDomainAssumeRoleWithTrust() { + assertEquals("trustdomain", authorizer.retrieveResourceDomain("resource", "assume_role", "trustdomain")); + } + + @Test + public void testRetrieveResourceDomainAssumeRoleWithOutTrust() { + assertEquals("domain1", authorizer.retrieveResourceDomain("domain1:resource", "assume_role", null)); + } + + @Test + public void testRetrieveResourceDomainValidDomain() { + assertEquals("domain1", authorizer.retrieveResourceDomain("domain1:resource", "read", null)); + assertEquals("domain1", authorizer.retrieveResourceDomain("domain1:resource", "read", "trustdomain")); + } + + @Test + public void testRetrieveResourceDomainInvalidResource() { + assertEquals(null, authorizer.retrieveResourceDomain("domain1:resource:invalid", "read", null)); + assertEquals(null, authorizer.retrieveResourceDomain("domain1:a:b:c:d:e", "read", "trustdomain")); + } + + + @Test + public void testCheckKerberosAuthorityAuthorization() { + Authority authority = new com.yahoo.athenz.auth.impl.KerberosAuthority(); + Principal principal = SimplePrincipal.create("krb", "user1", "v=U1;d=krb;n=user1;s=signature", + 0, authority); + assertTrue(authorizer.authorityAuthorizationAllowed(principal)); + } + + @Test + public void testCheckNullAuthorityAuthorization() { + Principal principal = SimplePrincipal.create("user", "joe", "v=U1;d=user;n=joe;s=signature", + 0, null); + assertTrue(authorizer.authorityAuthorizationAllowed(principal)); + } + + @Test + public void testMatchDelegatedTrustAssertionInvalidAction() { + + Assertion assertion = new Assertion(); + assertion.setAction("READ"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("domain:*"); + assertion.setRole("domain:role.Role"); + + assertFalse(authorizer.matchDelegatedTrustAssertion(assertion, null, null, null)); + } + + @Test + public void testMatchDelegatedTrustAssertionNoResPatternMatchWithOutPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("domain:role.Role"); + assertion.setRole("domain:role.Role"); + + assertFalse(authorizer.matchDelegatedTrustAssertion(assertion, "domain:role.Role2", null, null)); + assertFalse(authorizer.matchDelegatedTrustAssertion(assertion, "coretech:role.Role", null, null)); + } + + @Test + public void testMatchDelegatedTrustAssertionNoResPatternMatchWithPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("domain:role.Role"); + + assertFalse(authorizer.matchDelegatedTrustAssertion(assertion, "domain:role.Role2", null, null)); + assertFalse(authorizer.matchDelegatedTrustAssertion(assertion, "coretech:role.Role2", null, null)); + } + + @Test + public void testMatchDelegatedTrustAssertionNoRoleMatchWithPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("weather:role.*"); + + Role role = null; + List roles = new ArrayList<>(); + + role = createRoleObject("coretech", "readers", null); + roles.add(role); + + role = createRoleObject("coretech", "writers", null); + roles.add(role); + + role = createRoleObject("coretech", "updaters", null); + roles.add(role); + + assertFalse(authorizer.matchDelegatedTrustAssertion(assertion, "coretech:role.Role", null, roles)); + } + + @Test + public void testMatchDelegatedTrustAssertionNoRoleMatchWithOutPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("weather:role.Role"); + + Role role = null; + List roles = new ArrayList<>(); + + role = createRoleObject("coretech", "Role1", null); + roles.add(role); + + role = createRoleObject("coretech", "Role2", null); + roles.add(role); + + assertFalse(authorizer.matchDelegatedTrustAssertion(assertion, "weather:role.Role1", null, roles)); + assertFalse(authorizer.matchDelegatedTrustAssertion(assertion, "coretech:role.Role", null, roles)); + } + + @Test + public void testMatchDelegatedTrustAssertionNoMemberMatch() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("weather:role.Role"); + + Role role = null; + List roles = new ArrayList<>(); + + role = createRoleObject("weather", "Role1", null, "user_domain.user1", null); + roles.add(role); + + role = createRoleObject("weather", "Role", null, "user_domain.user2", null); + roles.add(role); + + assertFalse(authorizer.matchDelegatedTrustAssertion(assertion, "weather:role.Role", "user_domain.user1", roles)); + } + + @Test + public void testMatchDelegatedTrustAssertionValidWithPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("weather:role.*"); + + Role role = null; + List roles = new ArrayList<>(); + + role = createRoleObject("weather", "Role1", null, "user_domain.user1", null); + roles.add(role); + + role = createRoleObject("weather", "Role", null, "user_domain.user2", null); + roles.add(role); + + assertTrue(authorizer.matchDelegatedTrustAssertion(assertion, "weather:role.Role", "user_domain.user2", roles)); + } + + @Test + public void testMatchDelegatedTrustAssertionValidWithOutPattern() { + + Assertion assertion = new Assertion(); + assertion.setAction("ASSUME_ROLE"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*:role.Role"); + assertion.setRole("weather:role.Role"); + + Role role = null; + List roles = new ArrayList<>(); + + role = createRoleObject("weather", "Role1", null, "user_domain.user1", null); + roles.add(role); + + role = createRoleObject("weather", "Role", null, "user_domain.user2", null); + roles.add(role); + + assertTrue(authorizer.matchDelegatedTrustAssertion(assertion, "weather:role.Role", "user_domain.user2", roles)); + } + + @Test + public void testMatchDelegatedTrustPolicyNoAssertions() { + Policy policy = new Policy(); + assertFalse(authorizer.matchDelegatedTrustPolicy(policy, "roleName", "user_domain.user1", null)); + } + + @Test + public void testMatchPrincipalInRoleStdMemberMatch() { + + Role role = createRoleObject("weather", "Role", null, "user_domain.user2", null); + assertTrue(authorizer.matchPrincipalInRole(role, null, "user_domain.user2", null)); + } + + @Test + public void testMatchPrincipalInRoleStdMemberNoMatch() { + + Role role = createRoleObject("weather", "Role", null, "user_domain.user2", null); + assertFalse(authorizer.matchPrincipalInRole(role, null, "user_domain.user23", null)); + } + + @Test + public void testMatchPrincipalInRoleNoDelegatedTrust() { + Role role = createRoleObject("weather", "Role", null); + assertFalse(authorizer.matchPrincipalInRole(role, null, null, null)); + assertFalse(authorizer.matchPrincipalInRole(role, null, null, "weather")); + } + + @Test + public void testMatchPrincipalInRoleDelegatedTrustNoMatch() { + Role role = createRoleObject("weather", "Role", "coretech_not_present"); + assertFalse(authorizer.matchPrincipalInRole(role, "Role", "user_domain.user1", "coretech_not_present")); + } + + @Test + public void testMatchPrincipalInRoleDelegatedTrustMatch() { + + DataCache domain = new DataCache(); + DomainData domainData = new DomainData(); + domainData.setName("coretechtrust"); + domain.setDomainData(domainData); + domainData.setRoles(new ArrayList()); + Role role1 = createRoleObject("coretechtrust", "role1", null, "user_domain.user1", null); + Role role2 = createRoleObject("coretechtrust", "role2", null, "user_domain.user2", null); + domainData.getRoles().add(role1); + domainData.getRoles().add(role2); + + Policy policy = createPolicyObject("coretechtrust", "trust", "coretechtrust:role.role1", + false, "ASSUME_ROLE", "weather:role.role1", AssertionEffect.ALLOW); + domainData.setPolicies(new com.yahoo.athenz.zms.SignedPolicies()); + domainData.getPolicies().setContents(new com.yahoo.athenz.zms.DomainPolicies()); + domainData.getPolicies().getContents().setPolicies(new ArrayList()); + domainData.getPolicies().getContents().getPolicies().add(policy); + + store.getCacheStore().put("coretechtrust", domain); + Role role = createRoleObject("weather", "role1", "coretechtrust"); + assertTrue(authorizer.matchPrincipalInRole(role, "weather:role.role1", "user_domain.user1", "coretechtrust")); + assertFalse(authorizer.matchPrincipalInRole(role, "weather:role.role1", "user_domain.user1", "coretechtrust2")); + assertFalse(authorizer.matchPrincipalInRole(role, "weather:role.role1", "user_domain.user3", "coretechtrust")); + store.getCacheStore().invalidate("coretechtrust"); + } + + @Test + public void testAccessDelegatedTrust() { + + DataCache domain = new DataCache(); + DomainData domainData = new DomainData(); + domainData.setName("coretechtrust"); + domain.setDomainData(domainData); + domainData.setRoles(new ArrayList()); + Role role1 = createRoleObject("coretechtrust", "role1", null, "user_domain.user1", null); + Role role2 = createRoleObject("coretechtrust", "role2", null, "user_domain.user2", null); + domainData.getRoles().add(role1); + domainData.getRoles().add(role2); + + Policy policy = createPolicyObject("coretechtrust", "trust", "coretechtrust:role.role1", + false, "ASSUME_ROLE", "weather:role.role1", AssertionEffect.ALLOW); + domainData.setPolicies(new com.yahoo.athenz.zms.SignedPolicies()); + domainData.getPolicies().setContents(new com.yahoo.athenz.zms.DomainPolicies()); + domainData.getPolicies().getContents().setPolicies(new ArrayList()); + domainData.getPolicies().getContents().getPolicies().add(policy); + store.getCacheStore().put("coretechtrust", domain); + + domain = new DataCache(); + domainData = new DomainData(); + domainData.setName("weather"); + domain.setDomainData(domainData); + domainData.setRoles(new ArrayList()); + role1 = createRoleObject("weather", "role1", "coretechtrust"); + domainData.getRoles().add(role1); + + policy = createPolicyObject("weather", "access", "weather:role.role1", + false, "update", "weather:table1", AssertionEffect.ALLOW); + domainData.setPolicies(new com.yahoo.athenz.zms.SignedPolicies()); + domainData.getPolicies().setContents(new com.yahoo.athenz.zms.DomainPolicies()); + domainData.getPolicies().getContents().setPolicies(new ArrayList()); + domainData.getPolicies().getContents().getPolicies().add(policy); + store.getCacheStore().put("weather", domain); + + Principal principal1 = SimplePrincipal.create("user_domain", "user1", + "v=U1;d=user_domain;n=user1;s=signature", 0, null); + assertTrue(authorizer.access("update", "weather:table1", principal1, null)); + assertTrue(authorizer.access("update", "weather:table1", principal1, "coretechtrust")); + assertFalse(authorizer.access("update", "weather:table1", principal1, "unknowntrust")); + assertFalse(authorizer.access("update", "weather:table2", principal1, null)); + assertFalse(authorizer.access("delete", "weather:table1", principal1, null)); + + Principal principal2 = SimplePrincipal.create("user_domain", "user2", + "v=U1;d=user_domain;n=user2;s=signature", 0, null); + assertFalse(authorizer.access("update", "weather:table1", principal2, null)); + + Principal principal3 = SimplePrincipal.create("user_domain", "user3", + "v=U1;d=user_domain;n=user3;s=signature", 0, null); + assertFalse(authorizer.access("update", "weather:table1", principal3, null)); + + store.getCacheStore().invalidate("coretechtrust"); + store.getCacheStore().invalidate("weather"); + } + + @Test + public void testAccess() { + + DataCache domain = new DataCache(); + DomainData domainData = new DomainData(); + domainData.setName("coretechtrust"); + domain.setDomainData(domainData); + domainData.setRoles(new ArrayList()); + Role role1 = createRoleObject("coretechtrust", "role1", null, "user_domain.user1", null); + Role role2 = createRoleObject("coretechtrust", "role2", null, "user_domain.user2", null); + domainData.getRoles().add(role1); + domainData.getRoles().add(role2); + + Policy policy = createPolicyObject("coretechtrust", "access", "coretechtrust:role.role1", + false, "update", "coretechtrust:table1", AssertionEffect.ALLOW); + domainData.setPolicies(new com.yahoo.athenz.zms.SignedPolicies()); + domainData.getPolicies().setContents(new com.yahoo.athenz.zms.DomainPolicies()); + domainData.getPolicies().getContents().setPolicies(new ArrayList()); + domainData.getPolicies().getContents().getPolicies().add(policy); + store.getCacheStore().put("coretechtrust", domain); + + Principal principal1 = SimplePrincipal.create("user_domain", "user1", + "v=U1;d=user_domain;n=user1;s=signature", 0, null); + assertTrue(authorizer.access("update", "coretechtrust:table1", principal1, null)); + assertFalse(authorizer.access("update", "coretechtrust:table2", principal1, null)); + assertFalse(authorizer.access("delete", "coretechtrust:table1", principal1, null)); + + Principal principal2 = SimplePrincipal.create("user_domain", "user2", + "v=U1;d=user_domain;n=user2;s=signature", 0, null); + assertFalse(authorizer.access("update", "coretechtrust:table1", principal2, null)); + + Principal principal3 = SimplePrincipal.create("user_domain", "user3", + "v=U1;d=user_domain;n=user3;s=signature", 0, null); + assertFalse(authorizer.access("update", "coretechtrust:table1", principal3, null)); + + store.getCacheStore().invalidate("coretechtrust"); + } + + @Test + public void testAccessInvalidResource() { + + Principal principal1 = SimplePrincipal.create("user_domain", "user1", + "v=U1;d=user_domain;n=user1;s=signature", 0, null); + try { + authorizer.access("update", "coretechtrust:table1:test3", principal1, null); + fail(); + } catch (com.yahoo.athenz.zts.ResourceException ex) { + assertEquals(404, ex.getCode()); + } + } + + @Test + public void testAccessInvalidDomain() { + + Principal principal1 = SimplePrincipal.create("user_domain", "user1", + "v=U1;d=user_domain;n=user1;s=signature", 0, null); + try { + authorizer.access("update", "unknowndoamin:table1", principal1, null); + fail(); + } catch (com.yahoo.athenz.zts.ResourceException ex) { + assertEquals(404, ex.getCode()); + } + } + + @Test + public void testIsMemberOfRoleNoMembers() { + Role role1 = new Role(); + assertFalse(authorizer.isMemberOfRole(role1, "user_domain.user1")); + } + + @Test + public void testPostAWSCertificateRequestNoAwsAccount() { + + CloudStore cloudStore = new MockCloudStore(); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", null); + store.processDomain(signedDomain, false); + AWSCertificateRequest req = new AWSCertificateRequest(); + req.setCsr("csr"); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", + 0, new com.yahoo.athenz.auth.impl.CertificateAuthority()); + ResourceContext context = createResourceContext(principal); + + try { + zts.postAWSCertificateRequest(context, "athenz.product", "zts", req); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testPostAWSCertificateRequestNoCloudStore() { + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", null); + store.processDomain(signedDomain, false); + AWSCertificateRequest req = new AWSCertificateRequest(); + req.setCsr("csr"); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", + 0, new com.yahoo.athenz.auth.impl.CertificateAuthority()); + ResourceContext context = createResourceContext(principal); + + try { + zts.postAWSCertificateRequest(context, "athenz.product", "zts", req); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testPostAWSCertificateRequestInvalidAuthority() { + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", null); + store.processDomain(signedDomain, false); + AWSCertificateRequest req = new AWSCertificateRequest(); + req.setCsr("csr"); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", + 0, new com.yahoo.athenz.auth.impl.KerberosAuthority()); + ResourceContext context = createResourceContext(principal); + + try { + zts.postAWSCertificateRequest(context, "athenz.product", "zts", req); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + } + } + + @Test + public void testPostAWSCertificateRequestNullAuthority() { + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", null); + store.processDomain(signedDomain, false); + AWSCertificateRequest req = new AWSCertificateRequest(); + req.setCsr("csr"); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + + ResourceContext context = createResourceContext(principal); + + try { + zts.postAWSCertificateRequest(context, "athenz.product", "zts", req); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + } + } + + @Test + public void testPostAWSCertificateRequest() throws IOException { + + File caCert = new File("src/test/resources/valid_cn_x509.cert"); + X509Certificate caCertificate = Crypto.loadX509Certificate(caCert); + + File caKey = new File("src/test/resources/private_encrypted.key"); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(caKey, "athenz"); + CertSigner certSigner = new MockCertSigner(caPrivateKey, caCertificate); + + CloudStore cloudStore = new MockCloudStore(certSigner); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz", "012345"); + store.processDomain(signedDomain, false); + + AWSCertificateRequest req = new AWSCertificateRequest(); + Path path = Paths.get("src/test/resources/valid.csr"); + String certStr = new String(Files.readAllBytes(path)); + req.setCsr(certStr); + + Principal principal = SimplePrincipal.create("athenz", "syncer", "user-credentials", + 0, new com.yahoo.athenz.auth.impl.CertificateAuthority()); + ResourceContext context = createResourceContext(principal); + + Identity identity = zts.postAWSCertificateRequest(context, "athenz", "syncer", req); + assertNotNull(identity); + } + + @Test + public void testPostAWSCertificateRequestInvalidCSR() throws IOException { + + File caCert = new File("src/test/resources/valid_cn_x509.cert"); + X509Certificate caCertificate = Crypto.loadX509Certificate(caCert); + + File caKey = new File("src/test/resources/private_encrypted.key"); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(caKey, "athenz"); + CertSigner certSigner = new MockCertSigner(caPrivateKey, caCertificate); + + CloudStore cloudStore = new MockCloudStore(certSigner); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz", "012345"); + store.processDomain(signedDomain, false); + + AWSCertificateRequest req = new AWSCertificateRequest(); + req.setCsr("invalid-csr"); + + Principal principal = SimplePrincipal.create("athenz", "syncer", + "v=U1;d=athenz;n=syncer;s=signature", + 0, new com.yahoo.athenz.auth.impl.CertificateAuthority()); + ResourceContext context = createResourceContext(principal); + + try { + zts.postAWSCertificateRequest(context, "athenz", "syncer", req); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + } + + @Test + public void testPostAWSCertificateRequestMismatchPrincipal() throws IOException { + + File caCert = new File("src/test/resources/valid_cn_x509.cert"); + X509Certificate caCertificate = Crypto.loadX509Certificate(caCert); + + File caKey = new File("src/test/resources/private_encrypted.key"); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(caKey, "athenz"); + CertSigner certSigner = new MockCertSigner(caPrivateKey, caCertificate); + + CloudStore cloudStore = new MockCloudStore(certSigner); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz", "012345"); + store.processDomain(signedDomain, false); + + AWSCertificateRequest req = new AWSCertificateRequest(); + req.setCsr("invalid-csr"); + + Principal principal = SimplePrincipal.create("athenz", "zts", + "v=U1;d=athenz;n=zts;s=signature", + 0, new com.yahoo.athenz.auth.impl.CertificateAuthority()); + ResourceContext context = createResourceContext(principal); + + try { + zts.postAWSCertificateRequest(context, "athenz", "zts", req); + } catch (ResourceException ex) { + assertEquals(400, ex.getCode()); + } + } + + private AWSInstanceInformation generateAWSInstanceInformation(String domain, String account, + String document, String signature) { + + AWSInstanceInformation info = new AWSInstanceInformation(); + if (document == null) { + info.setDocument(CloudStoreTest.AWS_INSTANCE_DOCUMENT); + } else { + info.setDocument(document); + } + info.setSignature(signature); + + info.setName(domain + ".syncer"); + info.setDomain(domain); + info.setAccount(account); + info.setCloud("athenz"); + info.setService("syncer"); + info.setSubnet("subnet"); + info.setAccess("access"); + info.setSecret("secret"); + info.setToken("token"); + info.setExpires(Timestamp.fromCurrentTime()); + info.setModified(Timestamp.fromCurrentTime()); + info.setFlavor("AWS-HMAC"); + + Path path = Paths.get("src/test/resources/valid.csr"); + try { + String certCsr = new String(Files.readAllBytes(path)); + info.setCsr(certCsr); + } catch (IOException e) { + } + + return info; + } + + @Test + public void testPostInstanceInformation() throws IOException { + Path path = Paths.get("src/test/resources/valid.csr"); + String certCsr = new String(Files.readAllBytes(path)); + + InstanceInformation info = new InstanceInformation() + .setCsr(certCsr) + .setDocument("Test Document") + .setSignature("Test Signature") + .setDomain("athenz") + .setService("syncer") + .setKeyId("0"); + + Identity identity = zts.postInstanceInformation(null, info); + + assertNotNull(identity); + + X509Certificate cert = Crypto.loadX509Certificate(identity.getCertificate()); + assertNotNull(cert); + } + + @Test + public void testPostInstanceInformationInvalidCsr() throws IOException { + InstanceInformation info = new InstanceInformation() + .setCsr("invalid-csr") + .setDocument("Test Document") + .setSignature("Test Signature") + .setDomain("iaas.athenz") + .setService("syncer"); + + try { + zts.postInstanceInformation(null, info); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testPostAWSInstanceInformationNoAwsAccount() { + + CloudStore cloudStore = new MockCloudStore(); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", null); + store.processDomain(signedDomain, false); + + AWSInstanceInformation info = generateAWSInstanceInformation("athenz.product", + "111111111111", null, null); + + try { + zts.postAWSInstanceInformation(null, info); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testPostAWSInstanceInformationInfoAwsAccountMismatch() { + + CloudStore cloudStore = new MockCloudStore(); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "12345"); + store.processDomain(signedDomain, false); + + AWSInstanceInformation info = generateAWSInstanceInformation("athenz.product", + "111111111111", null, null); + + try { + zts.postAWSInstanceInformation(null, info); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testPostAWSInstanceInformationNoCloudStore() { + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", null); + store.processDomain(signedDomain, false); + + AWSInstanceInformation info = generateAWSInstanceInformation("athenz.product", + "111111111111", null, null); + + try { + zts.postAWSInstanceInformation(null, info); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testPostAWSInstanceInformationInvalidSignature() { + + CloudStore cloudStore = new MockCloudStore(); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "111111111111"); + store.processDomain(signedDomain, false); + + AWSInstanceInformation info = generateAWSInstanceInformation("athenz.product", + "111111111111", null, "invalid-signature"); + + try { + zts.postAWSInstanceInformation(null, info); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testPostAWSInstanceInformationInvalidDocument() { + + MockCloudStore cloudStore = new MockCloudStore(); + cloudStore.skipDocumentSignatureCheck(true); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "111111111111"); + store.processDomain(signedDomain, false); + + AWSInstanceInformation info = generateAWSInstanceInformation("athenz.product", + "111111111111", "invalid-document", null); + + try { + zts.postAWSInstanceInformation(null, info); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testPostAWSInstanceInformationAccountMismatch() { + + MockCloudStore cloudStore = new MockCloudStore(); + cloudStore.skipDocumentSignatureCheck(true); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "12345"); + store.processDomain(signedDomain, false); + + AWSInstanceInformation info = generateAWSInstanceInformation("athenz.product", + "12345", null, null); + + try { + zts.postAWSInstanceInformation(null, info); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testPostAWSInstanceInformationInvalidBootTime() { + + MockCloudStore cloudStore = new MockCloudStore(); + cloudStore.setIdentityCheckResult(1); + cloudStore.skipDocumentSignatureCheck(true); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "111111111111"); + store.processDomain(signedDomain, false); + + // our default limit is 300 seconds so we're going to set + // the pending time to before 301 seconds to fail + + long pendingTime = System.currentTimeMillis() - 301 * 1000; + String instanceDocument = AWS_INSTANCE_DOCUMENT_WOUT_TIMESTAMP_BEGIN + + Timestamp.fromMillis(pendingTime).toString() + + AWS_INSTANCE_DOCUMENT_WOUT_TIMESTAMP_END; + + AWSInstanceInformation info = generateAWSInstanceInformation("athenz.product", + "111111111111", instanceDocument, "signature"); + + try { + zts.postAWSInstanceInformation(null, info); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 403); + } + } + + @Test + public void testPostAWSInstanceInformationIdentityCheckFailure() { + + MockCloudStore cloudStore = new MockCloudStore(); + cloudStore.setIdentityCheckResult(-1); + cloudStore.skipDocumentSignatureCheck(true); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "111111111111"); + store.processDomain(signedDomain, false); + + long pendingTime = System.currentTimeMillis(); + String instanceDocument = AWS_INSTANCE_DOCUMENT_WOUT_TIMESTAMP_BEGIN + + Timestamp.fromMillis(pendingTime).toString() + + AWS_INSTANCE_DOCUMENT_WOUT_TIMESTAMP_END; + + AWSInstanceInformation info = generateAWSInstanceInformation("athenz.product", + "111111111111", instanceDocument, null); + + try { + zts.postAWSInstanceInformation(null, info); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testPostAWSInstanceInformationInvalidCSR() { + + MockCloudStore cloudStore = new MockCloudStore(); + cloudStore.setIdentityCheckResult(1); + cloudStore.skipDocumentSignatureCheck(true); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz.product", "111111111111"); + store.processDomain(signedDomain, false); + + long pendingTime = System.currentTimeMillis(); + String instanceDocument = AWS_INSTANCE_DOCUMENT_WOUT_TIMESTAMP_BEGIN + + Timestamp.fromMillis(pendingTime).toString() + + AWS_INSTANCE_DOCUMENT_WOUT_TIMESTAMP_END; + + AWSInstanceInformation info = generateAWSInstanceInformation("athenz.product", + "111111111111", instanceDocument, null); + info.setCsr("invalid-csr"); + + try { + zts.postAWSInstanceInformation(null, info); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testPostAWSInstanceInformation() throws IOException { + + File caCert = new File("src/test/resources/valid_cn_x509.cert"); + X509Certificate caCertificate = Crypto.loadX509Certificate(caCert); + + File caKey = new File("src/test/resources/private_encrypted.key"); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(caKey, "athenz"); + CertSigner certSigner = new MockCertSigner(caPrivateKey, caCertificate); + + MockCloudStore cloudStore = new MockCloudStore(certSigner); + cloudStore.setIdentityCheckResult(1); + cloudStore.skipDocumentSignatureCheck(true); + store.setCloudStore(cloudStore); + zts.cloudStore = cloudStore; + + SignedDomain signedDomain = createAwsSignedDomain("athenz", "111111111111"); + store.processDomain(signedDomain, false); + + long pendingTime = System.currentTimeMillis(); + String instanceDocument = AWS_INSTANCE_DOCUMENT_WOUT_TIMESTAMP_BEGIN + + Timestamp.fromMillis(pendingTime).toString() + + AWS_INSTANCE_DOCUMENT_WOUT_TIMESTAMP_END; + + Path path = Paths.get("src/test/resources/valid.csr"); + String certCsr = new String(Files.readAllBytes(path)); + + AWSInstanceInformation info = generateAWSInstanceInformation("athenz", + "111111111111", instanceDocument, "signature"); + info.setCsr(certCsr); + + Identity identity = zts.postAWSInstanceInformation(null, info); + assertNotNull(identity); + } + + @Test + public void testGetSchema() { + Schema schema = zts.getRdlSchema(null); + assertNotNull(schema); + } + + @Test + public void testGetSvcTokenExpiryTime() { + + long ZTS_NTOKEN_DEFAULT_EXPIRY = TimeUnit.SECONDS.convert(2, TimeUnit.HOURS); + long ZTS_NTOKEN_MAX_EXPIRY = TimeUnit.SECONDS.convert(7, TimeUnit.DAYS); + + assertEquals(zts.getSvcTokenExpiryTime(null), ZTS_NTOKEN_DEFAULT_EXPIRY); + assertEquals(zts.getSvcTokenExpiryTime(0), ZTS_NTOKEN_DEFAULT_EXPIRY); + assertEquals(zts.getSvcTokenExpiryTime(-1), ZTS_NTOKEN_DEFAULT_EXPIRY); + assertEquals(zts.getSvcTokenExpiryTime(100), 100); + assertEquals(zts.getSvcTokenExpiryTime(2 * 60 * 60), ZTS_NTOKEN_DEFAULT_EXPIRY); + assertEquals(zts.getSvcTokenExpiryTime(2 * 60 * 60 - 1), ZTS_NTOKEN_DEFAULT_EXPIRY - 1); + assertEquals(zts.getSvcTokenExpiryTime(604799), 604799); + assertEquals(zts.getSvcTokenExpiryTime(604800), ZTS_NTOKEN_MAX_EXPIRY); + assertEquals(zts.getSvcTokenExpiryTime(604801), ZTS_NTOKEN_MAX_EXPIRY); + } + + @Test + public void testPostInstanceRefreshRequestHcaCNMismatch() throws IOException { + Path path = Paths.get("src/test/resources/valid.csr"); + String certCsr = new String(Files.readAllBytes(path)); + + InstanceRefreshRequest req = new InstanceRefreshRequest().setCsr(certCsr); + + SimplePrincipal principal = (SimplePrincipal) SimplePrincipal.create("abc", "xyz", + "v=S1,d=abc;n=xyz;s=sig", 0, null); + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + ResourceContext context = createResourceContext(principal, servletRequest); + + try { + zts.postInstanceRefreshRequest(context, "abc", "xyz", req); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + + @Test + public void testPostInstanceRefreshRequestHcaPrincipalMismatch() throws IOException { + Path path = Paths.get("src/test/resources/valid.csr"); + String certCsr = new String(Files.readAllBytes(path)); + + InstanceRefreshRequest req = new InstanceRefreshRequest().setCsr(certCsr); + + SimplePrincipal principal = (SimplePrincipal) SimplePrincipal.create("abc", "xyz", + "v=S1,d=abc;n=xyz;s=sig", 0, null); + HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + ResourceContext context = createResourceContext(principal, servletRequest); + + try { + zts.postInstanceRefreshRequest(context, "iaas.athenz", "syncer", req); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + } + + @Test + public void testGetAccess() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + // user_domain.user only has access to writers + + Access access = zts.getAccess(context, "coretech", "writers", "user_domain.user"); + assertTrue(access.getGranted()); + + access = zts.getAccess(context, "coretech", "readers", "user_domain.user"); + assertFalse(access.getGranted()); + + // user_domain.user1 had access to readers and writers + + access = zts.getAccess(context, "coretech", "writers", "user_domain.user1"); + assertTrue(access.getGranted()); + + access = zts.getAccess(context, "coretech", "readers", "user_domain.user1"); + assertTrue(access.getGranted()); + + access = zts.getAccess(context, "coretech", "editors", "user_domain.user1"); + assertFalse(access.getGranted()); + + // user_domain.user4 only has access to readers + + access = zts.getAccess(context, "coretech", "readers", "user_domain.user4"); + assertTrue(access.getGranted()); + + access = zts.getAccess(context, "coretech", "writers", "user_domain.user4"); + assertFalse(access.getGranted()); + } + + @Test + public void testGetAccessInvalidData() { + + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", true); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + // null and empty arguments + + try { + zts.getAccess(context, "", "writers", "user_domain.user"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + try { + zts.getAccess(context, null, "writers", "user_domain.user"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + try { + zts.getAccess(context, "coretech", "", "user_domain.user"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + try { + zts.getAccess(context, "coretech", null, "user_domain.user"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + try { + zts.getAccess(context, "coretech", "writers", ""); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + try { + zts.getAccess(context, "coretech", "writers", null); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + } + + // now invalid domain + + try { + zts.getAccess(context, "coretech-unknown", "writers", "user_domain.user"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + } + } + + @Test + public void testPostDomainMetrics() { + SignedDomain signedDomain = createSignedDomain("coretech", "weather", "storage", false); + store.processDomain(signedDomain, false); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + // create zts with a metric we can verify + CloudStore cloudStore = new CloudStore(null); + cloudStore.setHttpClient(null); + CertSigner certSigner = new MockCertSignerFactory().create(null); + SvcCertStore svcCertStore = new YSvcCertStore(certSigner); + ZtsMetricTester metric = new ZtsMetricTester(); + ZTSImpl ztsImpl = new ZTSImpl("localhost", store, cloudStore, svcCertStore, metric, + privateKey, "0", AuditLogFactory.getLogger(), null); + + String testDomain = "coretech"; + + // create some metrics + List metricList = new ArrayList<>(); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED_DENY_NO_MATCH). + setMetricVal(99)); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED). + setMetricVal(27)); + DomainMetrics req = new DomainMetrics(). + setDomainName(testDomain). + setMetricList(metricList); + + // send the metrics + ztsImpl.postDomainMetrics(context, testDomain, req); + + // verify metrics were recorded + Map metrixMap = metric.getMap(); + String key = "dom_metric_" + + DomainMetricType.ACCESS_ALLOWED_DENY_NO_MATCH.toString().toLowerCase() + + testDomain; + Integer val = metrixMap.get(key); + assertNotNull(val); + assertEquals(val.intValue(), 99); + key = "dom_metric_" + + DomainMetricType.ACCESS_ALLOWED.toString().toLowerCase() + + testDomain; + val = metrixMap.get(key); + assertNotNull(val); + assertEquals(val.intValue(), 27); + + // test - failure case - invalid domain + // + testDomain = "not_coretech"; + // create some metrics + metricList = new ArrayList<>(); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED_DENY_NO_MATCH). + setMetricVal(999)); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED). + setMetricVal(277)); + req = new DomainMetrics(). + setDomainName(testDomain). + setMetricList(metricList); + + // send the metrics + metrixMap.clear(); + String errMsg = "No such domain"; + try { + ztsImpl.postDomainMetrics(context, testDomain, req); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 404); + assertTrue(ex.getMessage().contains(errMsg)); + } + + // verify no metrics were recorded + assertEquals(metrixMap.size(), 0); + + // test - failure case - missing domain name in metric data + // + testDomain = "coretech"; + // create some metrics + metricList = new ArrayList<>(); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED_DENY_NO_MATCH). + setMetricVal(999)); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED). + setMetricVal(277)); + req = new DomainMetrics(). + setMetricList(metricList); + + errMsg = "Missing required field: domainName"; + // send the metrics + metrixMap.clear(); + try { + ztsImpl.postDomainMetrics(context, testDomain, req); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains(errMsg), ex.getMessage()); + } + // verify no metrics were recorded + assertEquals(metrixMap.size(), 0); + + // test - failure case - mismatch domain in uri and metric data + // + testDomain = "coretech"; + // create some metrics + metricList = new ArrayList<>(); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED_DENY_NO_MATCH). + setMetricVal(999)); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED). + setMetricVal(277)); + req = new DomainMetrics(). + setDomainName("not_coretech"). + setMetricList(metricList); + + errMsg = "mismatched domain names"; + // send the metrics + metrixMap.clear(); + try { + ztsImpl.postDomainMetrics(context, testDomain, req); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains(errMsg), ex.getMessage()); + } + // verify no metrics were recorded + assertEquals(metrixMap.size(), 0); + + // test - failure case - empty metric list + // + testDomain = "coretech"; + // create some metrics + metricList = new ArrayList<>(); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED_DENY_NO_MATCH). + setMetricVal(999)); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED). + setMetricVal(277)); + req = new DomainMetrics(). + setDomainName(testDomain); + + errMsg = "Missing required field: metricList"; + // send the metrics + metrixMap.clear(); + try { + ztsImpl.postDomainMetrics(context, testDomain, req); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 400); + assertTrue(ex.getMessage().contains(errMsg), ex.getMessage()); + } + // verify no metrics were recorded + assertEquals(metrixMap.size(), 0); + + // test - failure case - metric count is missing + // + testDomain = "coretech"; + // create a single metric without a count + metricList = new ArrayList<>(); + metricList.add( + new DomainMetric(). + setMetricType(DomainMetricType.ACCESS_ALLOWED_DENY_NO_MATCH). + setMetricVal(-1)); + req = new DomainMetrics(). + setDomainName(testDomain). + setMetricList(metricList); + + // verify no metrics were recorded + metrixMap.clear(); + ztsImpl.postDomainMetrics(context, testDomain, req); + assertEquals(metrixMap.size(), 0); + } + + @Test + public void testIsAuthorizedProxyUser() { + assertFalse(zts.isAuthorizedProxyUser(null, "user.joe")); + + Set proxyUsers = new HashSet<>(); + proxyUsers.add("user.joe"); + proxyUsers.add("user.jane"); + + assertTrue(zts.isAuthorizedProxyUser(proxyUsers, "user.joe")); + assertTrue(zts.isAuthorizedProxyUser(proxyUsers, "user.jane")); + assertFalse(zts.isAuthorizedProxyUser(proxyUsers, "user.john")); + } + + @Test + public void testCompareRoleLists() { + List list1 = new ArrayList<>(); + List list2 = new ArrayList<>(); + + // emtpy sets should match + + assertTrue(zts.compareRoleLists(list1, list2)); + + // not the same size so mismatch + + list1.add("role1"); + list1.add("role2"); + + list2.add("role1"); + + assertFalse(zts.compareRoleLists(list1, list2)); + + // same size different values + + list2.add("role3"); + + assertFalse(zts.compareRoleLists(list1, list2)); + + // same values in both + + list1.add("role3"); + list2.add("role2"); + + assertTrue(zts.compareRoleLists(list1, list2)); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSJettyContainerTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSJettyContainerTest.java new file mode 100644 index 00000000000..12472797d75 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSJettyContainerTest.java @@ -0,0 +1,331 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts; + +import static org.testng.Assert.*; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.RequestLog; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.Slf4jRequestLog; +import org.eclipse.jetty.server.handler.RequestLogHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.thread.ThreadPool; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.yahoo.athenz.common.server.log.AthenzRequestLog; +import com.yahoo.athenz.common.server.log.AuditLogFactory; + +public class ZTSJettyContainerTest { + + @Mock com.yahoo.athenz.zts.ZTSHandler mockImpl; + + @BeforeClass + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @AfterMethod + public void cleanup() { + System.clearProperty(ZTSConsts.ZTS_PROP_MAX_THREADS); + System.clearProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PATH); + System.clearProperty(ZTSConsts.ZTS_PROP_KEYSTORE_TYPE); + System.clearProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PASSWORD); + System.clearProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PATH); + System.clearProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_TYPE); + System.clearProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PASSWORD); + System.clearProperty(ZTSConsts.ZTS_PROP_KEYMANAGER_PASSWORD); + System.clearProperty(ZTSConsts.ZTS_PROP_EXCLUDED_CIPHER_SUITES); + System.clearProperty(ZTSConsts.ZTS_PROP_EXCLUDED_PROTOCOLS); + System.clearProperty(ZTSConsts.ZTS_PROP_SEND_SERVER_VERSION); + System.clearProperty(ZTSConsts.ZTS_PROP_SEND_DATE_HEADER); + System.clearProperty(ZTSConsts.ZTS_PROP_OUTPUT_BUFFER_SIZE); + System.clearProperty(ZTSConsts.ZTS_PROP_REQUEST_HEADER_SIZE); + System.clearProperty(ZTSConsts.ZTS_PROP_RESPONSE_HEADER_SIZE); + System.clearProperty(ZTSConsts.ZTS_PROP_IDLE_TIMEOUT); + } + + @Test + public void testContainerThreadPool() { + + System.setProperty(ZTSConsts.ZTS_PROP_MAX_THREADS, "100"); + + ZTSJettyContainer container = new ZTSJettyContainer(AuditLogFactory.getLogger(), null); + container.createServer(100); + + Server server = container.getServer(); + assertNotNull(server); + + ThreadPool threadPool = server.getThreadPool(); + assertNotNull(threadPool); + + // at this point we have no threads so the value is 0 + assertEquals(threadPool.getThreads(), 0); + assertEquals(threadPool.getIdleThreads(), 0); + } + + @Test + public void testRequestLogHandler() { + + System.setProperty(ZTSConsts.ZTS_PROP_ACCESS_LOG_RETAIN_DAYS, "3"); + + ZTSJettyContainer container = new ZTSJettyContainer(AuditLogFactory.getLogger(), null); + container.resource(com.yahoo.athenz.zts.ZTSResources.class); + container.delegate(com.yahoo.athenz.zts.ZTSHandler.class, mockImpl); + container.createServer(100); + + container.addRequestLogHandler("/tmp/zts_log"); + + // now retrieve the request log handler + + Handler[] handlers = container.getHandlers().getHandlers(); + RequestLogHandler logHandler = null; + for (Handler handler : handlers) { + if (handler instanceof RequestLogHandler) { + logHandler = (RequestLogHandler) handler; + break; + } + } + + assertNotNull(logHandler); + + RequestLog reqLog = logHandler.getRequestLog(); + assertNotNull(reqLog); + assertEquals(reqLog.getClass(), AthenzRequestLog.class); + } + + @Test + public void testSlf4jRequestLogHandler() { + + System.setProperty(ZTSConsts.ZTS_PROP_ACCESS_SLF4J_LOGGER, "AthenzAccessLogger"); + + ZTSJettyContainer container = new ZTSJettyContainer(AuditLogFactory.getLogger(), null); + container.resource(com.yahoo.athenz.zts.ZTSResources.class); + container.delegate(com.yahoo.athenz.zts.ZTSHandler.class, mockImpl); + container.createServer(100); + + container.addRequestLogHandler("/tmp/zts_log"); + + // now retrieve the request log handler + + Handler[] handlers = container.getHandlers().getHandlers(); + RequestLogHandler logHandler = null; + for (Handler handler : handlers) { + if (handler instanceof RequestLogHandler) { + logHandler = (RequestLogHandler) handler; + break; + } + } + + assertNotNull(logHandler); + + RequestLog reqLog = logHandler.getRequestLog(); + assertNotNull(reqLog); + assertEquals(reqLog.getClass(), Slf4jRequestLog.class); + assertEquals(((Slf4jRequestLog) reqLog).getLoggerName(), "AthenzAccessLogger"); + System.clearProperty(ZTSConsts.ZTS_PROP_ACCESS_SLF4J_LOGGER); + } + + @Test + public void testPrimaryContext() { + + ZTSJettyContainer container = new ZTSJettyContainer(AuditLogFactory.getLogger(), null); + container.createServer(100); + container.resource(com.yahoo.athenz.zts.ZTSResources.class); + container.delegate(com.yahoo.athenz.zts.ZTSHandler.class, mockImpl); + container.addServletHandlers("/tmp", "localhost"); + + Handler[] handlers = container.getHandlers().getHandlers(); + ServletContextHandler srvHandler = null; + for (Handler handler : handlers) { + if (handler instanceof ServletContextHandler) { + srvHandler = (ServletContextHandler) handler; + break; + } + } + + assertNotNull(srvHandler); + assertEquals(srvHandler.getContextPath(), "/"); + } + + @Test + public void testHttpConfigurationValidHttpsPort() { + + ZTSJettyContainer container = new ZTSJettyContainer(AuditLogFactory.getLogger(), null); + container.createServer(100); + + System.setProperty(ZTSConsts.ZTS_PROP_SEND_SERVER_VERSION, "true"); + System.setProperty(ZTSConsts.ZTS_PROP_SEND_DATE_HEADER, "false"); + System.setProperty(ZTSConsts.ZTS_PROP_OUTPUT_BUFFER_SIZE, "128"); + System.setProperty(ZTSConsts.ZTS_PROP_REQUEST_HEADER_SIZE, "256"); + System.setProperty(ZTSConsts.ZTS_PROP_RESPONSE_HEADER_SIZE, "512"); + + int httpsPort = 443; + + HttpConfiguration httpConfig = container.newHttpConfiguration(httpsPort); + assertNotNull(httpConfig); + + assertEquals(httpConfig.getOutputBufferSize(), 128); + assertFalse(httpConfig.getSendDateHeader()); + assertTrue(httpConfig.getSendServerVersion()); + assertEquals(httpConfig.getRequestHeaderSize(), 256); + assertEquals(httpConfig.getResponseHeaderSize(), 512); + assertEquals(httpConfig.getSecurePort(), httpsPort); + assertEquals(httpConfig.getSecureScheme(), "https"); + } + + @Test + public void testHttpConfigurationNoHttpsPort() { + + ZTSJettyContainer container = new ZTSJettyContainer(AuditLogFactory.getLogger(), null); + container.createServer(100); + + System.setProperty(ZTSConsts.ZTS_PROP_SEND_SERVER_VERSION, "false"); + System.setProperty(ZTSConsts.ZTS_PROP_SEND_DATE_HEADER, "true"); + System.setProperty(ZTSConsts.ZTS_PROP_OUTPUT_BUFFER_SIZE, "64"); + System.setProperty(ZTSConsts.ZTS_PROP_REQUEST_HEADER_SIZE, "128"); + System.setProperty(ZTSConsts.ZTS_PROP_RESPONSE_HEADER_SIZE, "256"); + + HttpConfiguration httpConfig = container.newHttpConfiguration(0); + assertNotNull(httpConfig); + + assertEquals(httpConfig.getOutputBufferSize(), 64); + assertTrue(httpConfig.getSendDateHeader()); + assertFalse(httpConfig.getSendServerVersion()); + assertEquals(httpConfig.getRequestHeaderSize(), 128); + assertEquals(httpConfig.getResponseHeaderSize(), 256); + assertEquals(httpConfig.getSecurePort(), 0); + + // it defaults to https even if we have no value specified + assertEquals(httpConfig.getSecureScheme(), "https"); + } + + @Test + public void testHttpConnectorsBoth() { + + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PATH, "file:///tmp/keystore"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_TYPE, "PKCS12"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PATH, "file:///tmp/truststore"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYMANAGER_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_IDLE_TIMEOUT, "10001"); + + ZTSJettyContainer container = new ZTSJettyContainer(AuditLogFactory.getLogger(), null); + container.createServer(100); + + HttpConfiguration httpConfig = container.newHttpConfiguration(8082); + container.addHTTPConnectors(httpConfig, 8081, 8082); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 2); + + assertEquals(connectors[0].getIdleTimeout(), 10001); + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + + System.out.println("Connectors: " + connectors[1].getProtocols().toString()); + assertTrue(connectors[1].getProtocols().contains("http/1.1")); + assertTrue(connectors[1].getProtocols().contains("ssl")); + } + + @Test + public void testHttpConnectorsHttpsOnly() { + + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PATH, "file:///tmp/keystore"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_TYPE, "PKCS12"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PATH, "file:///tmp/truststore"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYMANAGER_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_IDLE_TIMEOUT, "10001"); + + ZTSJettyContainer container = new ZTSJettyContainer(AuditLogFactory.getLogger(), null); + container.createServer(100); + + HttpConfiguration httpConfig = container.newHttpConfiguration(8082); + container.addHTTPConnectors(httpConfig, 0, 8082); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 1); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + assertTrue(connectors[0].getProtocols().contains("ssl")); + } + + @Test + public void testHttpConnectorsHttpOnly() { + + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PATH, "file:///tmp/keystore"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_TYPE, "PKCS12"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PATH, "file:///tmp/truststore"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYMANAGER_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_IDLE_TIMEOUT, "10001"); + + ZTSJettyContainer container = new ZTSJettyContainer(AuditLogFactory.getLogger(), null); + container.resource(com.yahoo.athenz.zts.ZTSResources.class); + container.delegate(com.yahoo.athenz.zts.ZTSHandler.class, mockImpl); + container.createServer(100); + + HttpConfiguration httpConfig = container.newHttpConfiguration(0); + container.addHTTPConnectors(httpConfig, 8081, 0); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 1); + + assertEquals(connectors[0].getIdleTimeout(), 10001); + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + assertFalse(connectors[0].getProtocols().contains("ssl")); + } + + @Test + public void testFilter() { + + ZTSJettyContainer container = new ZTSJettyContainer(AuditLogFactory.getLogger(), null); + container.createServer(100); + container.resource(com.yahoo.athenz.zts.ZTSResources.class); + container.delegate(com.yahoo.athenz.zts.ZTSHandler.class, mockImpl); + container.addServletHandlers("/tmp", "localhost"); + + Handler[] handlers = container.getHandlers().getHandlers(); + ServletContextHandler srvHandler = null; + for (Handler handler : handlers) { + if (handler instanceof ServletContextHandler) { + srvHandler = (ServletContextHandler) handler; + break; + } + } + assertNotNull(srvHandler); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTest.java new file mode 100644 index 00000000000..c89252bd555 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTest.java @@ -0,0 +1,219 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zts; + +import static org.testng.Assert.*; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class ZTSTest { + + private static final String ZTS_CHANGE_LOG_STORE_CLASS = "com.yahoo.athenz.zts.store.file.MockZMSFileChangeLogStoreFactory"; + private static final String ZTS_SVC_CERT_STORE_CLASS = "com.yahoo.athenz.zts.cert.MockCertSignerFactory"; + + @BeforeClass + public void setUp() throws Exception { + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY_STORE_CLASS, "com.yahoo.athenz.zts.pkey.file.FilePrivateKeyStoreFactory"); + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY, "src/test/resources/zts_private.pem"); + System.setProperty(ZTSConsts.ZTS_PROP_ATHENZ_CONF, "src/test/resources/athenz.conf"); + System.setProperty("logback.configurationFile", "src/test/resources/logback.xml"); + } + + @BeforeMethod + public void prepare() { + } + + @AfterMethod + public void cleanup() { + System.clearProperty(ZTSConsts.ZTS_PROP_HOME); + System.clearProperty(ZTSConsts.ZTS_PROP_HOSTNAME); + System.clearProperty(ZTSConsts.ZTS_PROP_HTTP_PORT); + System.clearProperty(ZTSConsts.ZTS_PROP_HTTPS_PORT); + } + + @Test + public void testGetServerHostNamePropertySet() { + System.setProperty(ZTSConsts.ZTS_PROP_HOSTNAME, "MyTestHost"); + assertEquals(ZTS.getServerHostName(), "MyTestHost"); + } + + @Test + public void testGetServerHostNameNoProperty() { + assertNotNull(ZTS.getServerHostName()); + } + + @Test + public void initContainerValidPorts() { + System.setProperty(ZTSConsts.ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS, ZTS_CHANGE_LOG_STORE_CLASS); + System.setProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_CLASS, ZTS_SVC_CERT_STORE_CLASS); + + System.setProperty(ZTSConsts.ZTS_PROP_HOME, "/tmp/zts_server"); + System.setProperty(ZTSConsts.ZTS_PROP_HTTP_PORT, "4080"); + System.setProperty(ZTSConsts.ZTS_PROP_HTTPS_PORT, "4443"); + + ZTSJettyContainer container = ZTS.createJettyContainer(); + assertNotNull(container); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 2); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + + System.out.println("Connectors: " + connectors[1].getProtocols().toString()); + assertTrue(connectors[1].getProtocols().contains("http/1.1")); + assertTrue(connectors[1].getProtocols().contains("ssl")); + + System.clearProperty(ZTSConsts.ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS); + System.clearProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_CLASS); + + } + + @Test + public void initContainerOnlyHTTPSPort() { + System.setProperty(ZTSConsts.ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS, ZTS_CHANGE_LOG_STORE_CLASS); + System.setProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_CLASS, ZTS_SVC_CERT_STORE_CLASS); + + System.setProperty(ZTSConsts.ZTS_PROP_HOME, "/tmp/zts_server"); + System.setProperty(ZTSConsts.ZTS_PROP_HTTP_PORT, "0"); + System.setProperty(ZTSConsts.ZTS_PROP_HTTPS_PORT, "4443"); + + ZTSJettyContainer container = ZTS.createJettyContainer(); + assertNotNull(container); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 1); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + assertTrue(connectors[0].getProtocols().contains("ssl")); + + System.clearProperty(ZTSConsts.ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS); + System.clearProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_CLASS); + } + + @Test + public void initContainerOnlyHTTPPort() { + System.setProperty(ZTSConsts.ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS, ZTS_CHANGE_LOG_STORE_CLASS); + System.setProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_CLASS, ZTS_SVC_CERT_STORE_CLASS); + + System.setProperty(ZTSConsts.ZTS_PROP_HOME, "/tmp/zts_server"); + System.setProperty(ZTSConsts.ZTS_PROP_HTTP_PORT, "4080"); + System.setProperty(ZTSConsts.ZTS_PROP_HTTPS_PORT, "0"); + + ZTSJettyContainer container = ZTS.createJettyContainer(); + assertNotNull(container); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 1); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + assertFalse(connectors[0].getProtocols().contains("ssl")); + + System.clearProperty(ZTSConsts.ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS); + System.clearProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_CLASS); + } + + @Test + public void initContainerInvalidHTTPPort() { + System.setProperty(ZTSConsts.ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS, ZTS_CHANGE_LOG_STORE_CLASS); + System.setProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_CLASS, ZTS_SVC_CERT_STORE_CLASS); + + System.setProperty(ZTSConsts.ZTS_PROP_HOME, "/tmp/zts_server"); + System.setProperty(ZTSConsts.ZTS_PROP_HTTP_PORT, "-10"); + System.setProperty(ZTSConsts.ZTS_PROP_HTTPS_PORT, "4443"); + + ZTSJettyContainer container = ZTS.createJettyContainer(); + assertNotNull(container); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 2); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + + System.out.println("Connectors: " + connectors[1].getProtocols().toString()); + assertTrue(connectors[1].getProtocols().contains("http/1.1")); + assertTrue(connectors[1].getProtocols().contains("ssl")); + + System.clearProperty(ZTSConsts.ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS); + System.clearProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_CLASS); + } + + @Test + public void initContainerInvalidHTTPSPort() { + System.setProperty(ZTSConsts.ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS, ZTS_CHANGE_LOG_STORE_CLASS); + System.setProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_CLASS, ZTS_SVC_CERT_STORE_CLASS); + + System.setProperty(ZTSConsts.ZTS_PROP_HOME, "/tmp/zts_server"); + System.setProperty(ZTSConsts.ZTS_PROP_HTTP_PORT, "4080"); + System.setProperty(ZTSConsts.ZTS_PROP_HTTPS_PORT, "-10"); + + ZTSJettyContainer container = ZTS.createJettyContainer(); + assertNotNull(container); + + Server server = container.getServer(); + Connector[] connectors = server.getConnectors(); + assertEquals(connectors.length, 1); + + System.out.println("Connectors: " + connectors[0].getProtocols().toString()); + assertTrue(connectors[0].getProtocols().contains("http/1.1")); + assertFalse(connectors[0].getProtocols().contains("ssl")); + + System.clearProperty(ZTSConsts.ZTS_PROP_DATA_CHANGE_LOG_STORE_CLASS); + System.clearProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_CLASS); + } + + @Test + public void testGetPortNumberDefault() { + assertEquals(ZTS.getPortNumber("NotExistantProperty", 4080), 4080); + } + + @Test + public void testGetPortNumberValid() { + System.setProperty(ZTSConsts.ZTS_PROP_HTTP_PORT, "4085"); + assertEquals(ZTS.getPortNumber(ZTSConsts.ZTS_PROP_HTTP_PORT, 4080), 4085); + } + + @Test + public void testGetPortNumberInvalidFormat() { + System.setProperty(ZTSConsts.ZTS_PROP_HTTP_PORT, "abc"); + assertEquals(ZTS.getPortNumber(ZTSConsts.ZTS_PROP_HTTP_PORT, 4080), 4080); + } + + @Test + public void testGetPortNumberOutOfRangeNegative() { + System.setProperty(ZTSConsts.ZTS_PROP_HTTP_PORT, "-1"); + assertEquals(ZTS.getPortNumber(ZTSConsts.ZTS_PROP_HTTP_PORT, 4080), 4080); + } + + @Test + public void testGetPortNumberOutOfRangePositive() { + System.setProperty(ZTSConsts.ZTS_PROP_HTTP_PORT, "65536"); + assertEquals(ZTS.getPortNumber(ZTSConsts.ZTS_PROP_HTTP_PORT, 4080), 4080); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/cache/DataCacheTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/cache/DataCacheTest.java new file mode 100644 index 00000000000..48f9fe7b8de --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/cache/DataCacheTest.java @@ -0,0 +1,862 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cache; + +import static org.testng.Assert.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.zms.Assertion; +import com.yahoo.athenz.zms.AssertionEffect; +import com.yahoo.athenz.zms.Domain; +import com.yahoo.athenz.zms.DomainData; +import com.yahoo.athenz.zms.Policy; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.ServiceIdentity; +import com.yahoo.athenz.zts.cache.DataCache; + +public class DataCacheTest { + + static final String ZTS_Y64_CERT0 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RR" + + "RUJBUVVBQTRHTkFEQ0JpUUtCZ1FDMXRHU1ZDQTh3bDVldzVZNzZXajJySkFVRApZYW5FSmZLbUFseDVjUS84a" + + "EtFVWZTU2dwWHIzQ3pkaDFhMjZkbGI3bW1LMjlxbVhKWGg2dW1XOUF5ZlRPS1ZvCis2QVNsb1ZVM2F2dnVmbE" + + "dVT0VnMmpzbWRha1IyNEtjTGpBdTZRclVlNDE3bEczdDhxU1BJR2pTNUMrQ3NKVXcKaDA0aEh4NWYrUEV3eFY" + + "0cmJRSURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + static final String ZTS_PEM_CERT0 = "-----BEGIN PUBLIC KEY-----\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1tGSVCA8wl5ew5Y76Wj2rJAUD\n" + + "YanEJfKmAlx5cQ/8hKEUfSSgpXr3Czdh1a26dlb7mmK29qmXJXh6umW9AyfTOKVo\n" + + "+6ASloVU3avvuflGUOEg2jsmdakR24KcLjAu6QrUe417lG3t8qSPIGjS5C+CsJUw\n" + + "h04hHx5f+PEwxV4rbQIDAQAB\n" + + "-----END PUBLIC KEY-----\n"; + static final String ZTS_Y64_CERT1 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RR" + + "RUJBUVVBQTRHTkFEQ0JpUUtCZ1FETUhWaFRNZldJQWdvTEdhbkx2QkNNRytRdAoySU9pcml2cGRLSFNPSkpsYX" + + "VKRUNlWlY1MTVmWG91SjhRb09IczA4UGlsdXdjeHF5dmhJSlduNWFrVEhGSWh5CkdDNkdtUTUzbG9WSEtTVE1WO" + + "DM1M0FjNkhydzYxbmJZMVQ2TnA2bjdxdXI4a1UwR2tmdk5hWFZrK09LNVBaankKbkxzZ251UjlCeFZndlM4ZjJR" + + "SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + static final String ZTS_PEM_CERT1 = "-----BEGIN PUBLIC KEY-----\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMHVhTMfWIAgoLGanLvBCMG+Qt\n" + + "2IOirivpdKHSOJJlauJECeZV515fXouJ8QoOHs08PiluwcxqyvhIJWn5akTHFIhy\n" + + "GC6GmQ53loVHKSTMV8353Ac6Hrw61nbY1T6Np6n7qur8kU0GkfvNaXVk+OK5PZjy\n" + + "nLsgnuR9BxVgvS8f2QIDAQAB\n" + + "-----END PUBLIC KEY-----\n"; + static final String ZTS_Y64_CERT2 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RR" + + "RUJBUVVBQTRHTkFEQ0JpUUtCZ1FEbmZsZVZ4d293aitRWStjQi8rbWs5YXZYZgpHUWVpTTdOMlMwby9LV3FWK2h" + + "GVWtDZkExMWxEYVJoZUY0alFhSzVaM2pPUE9nbklOZE5hd3VXQ081NUxKdVJRCmI1R0ZSbzhPNjNJNzA3M3ZDZ0V" + + "KdmNST09SdjJDYWhQbnBKbjc3bkhQdlV2Szl0M3JyRURhdi8vanA0UDN5REMKNEVNdHBScmduUXBXNmpJSWlRSUR" + + "BUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + static final String ZTS_PEM_CERT2 = "-----BEGIN PUBLIC KEY-----\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDnfleVxwowj+QY+cB/+mk9avXf\n" + + "GQeiM7N2S0o/KWqV+hFUkCfA11lDaRheF4jQaK5Z3jOPOgnINdNawuWCO55LJuRQ\n" + + "b5GFRo8O63I7073vCgEJvcROORv2CahPnpJn77nHPvUvK9t3rrEDav//jp4P3yDC\n" + + "4EMtpRrgnQpW6jIIiQIDAQAB\n" + + "-----END PUBLIC KEY-----\n"; + static final String ZTS_Y64_CERT3 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RR" + + "RUJBUVVBQTRHTkFEQ0JpUUtCZ1FETWRqSmUwY01wSGR4ZEJKTDcvR2poNTNVUAp5WTdVQ2VlYnZUa2M2S1ZmR0" + + "RnVVlrMUhtaWJ5U21lbnZOYitkNkhXQ1YySGVicUptN1krL2VuaFNkcTR3QTJrCnFtdmFHY09rV1R2cUU2a2J1" + + "MG5LemdUK21jck1sOVpqTHdBQXZPS1hTRi82MTJxQ0tlSElRd3ZtWlB1RkJJTjEKUnFteWgwT0k1aHN5VS9nYj" + + "Z3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + static final String ZTS_PEM_CERT3 = "-----BEGIN PUBLIC KEY-----\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMdjJe0cMpHdxdBJL7/Gjh53UP\n" + + "yY7UCeebvTkc6KVfGDgUYk1HmibySmenvNb+d6HWCV2HebqJm7Y+/enhSdq4wA2k\n" + + "qmvaGcOkWTvqE6kbu0nKzgT+mcrMl9ZjLwAAvOKXSF/612qCKeHIQwvmZPuFBIN1\n" + + "Rqmyh0OI5hsyU/gb6wIDAQAB\n" + + "-----END PUBLIC KEY-----\n"; + + @Test + public void testDomainSetGet() { + + DomainData domain = new DomainData(); + domain.setName("testDomain"); + + DataCache cache = new DataCache(); + cache.setDomainData(domain); + + DomainData dom = cache.getDomainData(); + assertNotNull(dom); + assertEquals(dom.getName(), "testDomain"); + } + + @Test + public void testRoleNoMembers() { + + Role role = new Role(); + role.setName("dom.role1"); + + DataCache cache = new DataCache(); + cache.processRole(role); + + Set set1 = cache.getMemberRoleSet("user_domain.user1"); + assertNull(set1); + } + + @Test + public void testSimpleRole() { + + Role role = new Role(); + role.setName("dom.role1"); + + List members = new ArrayList(); + members.add("user_domain.user1"); + members.add("user_domain.user2"); + role.setMembers(members); + + DataCache cache = new DataCache(); + cache.processRole(role); + + Set set1 = cache.getMemberRoleSet("user_domain.user1"); + assertNotNull(set1); + assertTrue(set1.contains("dom.role1")); + assertEquals(set1.size(), 1); + + Set set2 = cache.getMemberRoleSet("user_domain.user2"); + assertNotNull(set2); + assertTrue(set2.contains("dom.role1")); + assertEquals(set2.size(), 1); + + Set set3 = cache.getMemberRoleSet("user_domain.user3"); + assertNull(set3); + + Map> map = cache.getTrustMap(); + assertNotNull(map); + assertEquals(map.size(), 0); + } + + @Test + public void testSimpleRoleDuplicateMember() { + + Role role = new Role(); + role.setName("dom.role1"); + + List members = new ArrayList(); + members.add("user_domain.user1"); + members.add("user_domain.user2"); + members.add("user_domain.user1"); + members.add("user_domain.user1"); + role.setMembers(members); + + DataCache cache = new DataCache(); + cache.processRole(role); + + Set set1 = cache.getMemberRoleSet("user_domain.user1"); + assertNotNull(set1); + assertTrue(set1.contains("dom.role1")); + assertEquals(set1.size(), 1); + + Set set2 = cache.getMemberRoleSet("user_domain.user2"); + assertNotNull(set2); + assertTrue(set2.contains("dom.role1")); + assertEquals(set2.size(), 1); + + Set set3 = cache.getMemberRoleSet("user_domain.user3"); + assertNull(set3); + + Map> map = cache.getTrustMap(); + assertNotNull(map); + assertEquals(map.size(), 0); + } + + @Test + public void testMultipleRoles() { + + Role role1 = new Role(); + role1.setName("dom.role1"); + + List members1 = new ArrayList(); + members1.add("user_domain.user1"); + members1.add("user_domain.user2"); + role1.setMembers(members1); + + Role role2 = new Role(); + role2.setName("dom.role2"); + + List members2 = new ArrayList(); + members2.add("user_domain.user2"); + members2.add("user_domain.user3"); + role2.setMembers(members2); + + DataCache cache = new DataCache(); + + cache.processRole(role1); + cache.processRole(role2); + + Set set1 = cache.getMemberRoleSet("user_domain.user1"); + assertNotNull(set1); + assertTrue(set1.contains("dom.role1")); + assertEquals(set1.size(), 1); + + Set set2 = cache.getMemberRoleSet("user_domain.user2"); + assertNotNull(set2); + assertTrue(set2.contains("dom.role1")); + assertTrue(set2.contains("dom.role2")); + assertEquals(set2.size(), 2); + + Set set3 = cache.getMemberRoleSet("user_domain.user3"); + assertNotNull(set3); + assertTrue(set3.contains("dom.role2")); + assertEquals(set3.size(), 1); + + Set set4 = cache.getMemberRoleSet("user_domain.user4"); + assertNull(set4); + } + + @Test + public void testProcessRoleMembers() { + + List members1 = new ArrayList(); + members1.add("user_domain.user1"); + members1.add("user_domain.user2"); + + DataCache cache = new DataCache(); + cache.processRoleMembers("dom.role1", members1); + + Set set1 = cache.getMemberRoleSet("user_domain.user1"); + assertNotNull(set1); + assertTrue(set1.contains("dom.role1")); + assertEquals(set1.size(), 1); + + Set set2 = cache.getMemberRoleSet("user_domain.user2"); + assertNotNull(set2); + assertTrue(set2.contains("dom.role1")); + assertEquals(set2.size(), 1); + } + + @Test + public void testRoleWithTrust() { + + Role role1 = new Role(); + role1.setName("dom.role1"); + role1.setTrust("dom2"); + + DataCache cache = new DataCache(); + cache.processRole(role1); + + Map> map = cache.getTrustMap(); + assertNotNull(map); + assertEquals(map.size(), 1); + assertTrue(map.containsKey("dom2")); + assertEquals(map.get("dom2").size(), 1); + assertTrue(map.get("dom2").contains("dom.role1")); + } + + @Test + public void testProcessRoleTrustDomain() { + + DataCache cache = new DataCache(); + cache.processRoleTrustDomain("dom.role1", "trustD"); + + Map> map = cache.getTrustMap(); + assertNotNull(map); + assertEquals(map.size(), 1); + assertTrue(map.containsKey("trustD")); + assertEquals(map.get("trustD").size(), 1); + assertTrue(map.get("trustD").contains("dom.role1")); + } + + @Test + public void testRolesWithTrust() { + + Role role1 = new Role(); + role1.setName("dom.role1"); + role1.setTrust("dom2"); + + Role role2 = new Role(); + role2.setName("dom.role2"); + role2.setTrust("dom3"); + + Role role3 = new Role(); + role3.setName("dom.role3"); + role3.setTrust("dom3"); + + DataCache cache = new DataCache(); + cache.processRole(role1); + cache.processRole(role2); + cache.processRole(role3); + + Map> map = cache.getTrustMap(); + assertNotNull(map); + assertEquals(map.size(), 2); + + assertTrue(map.containsKey("dom2")); + assertEquals(map.get("dom2").size(), 1); + assertTrue(map.get("dom2").contains("dom.role1")); + + assertTrue(map.containsKey("dom3")); + assertEquals(map.get("dom3").size(), 2); + assertTrue(map.get("dom3").contains("dom.role2")); + assertTrue(map.get("dom3").contains("dom.role3")); + } + + @Test + public void testPolicyWithAssertions() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + Role role1 = new Role(); + role1.setName("testDomain.role.role1"); + + List members1 = new ArrayList(); + members1.add("user_domain.user1"); + members1.add("user_domain.user2"); + role1.setMembers(members1); + + Role role2 = new Role(); + role2.setName("testDomain.role.role2"); + + List members2 = new ArrayList(); + members2.add("user_domain.user2"); + role2.setMembers(members2); + + Role role3 = new Role(); + role3.setName("testDomain.role.role3"); + + List members3 = new ArrayList(); + members3.add("user_domain.user3"); + role3.setMembers(members3); + + Policy policy = new Policy(); + policy.setName("testDomain.policy.policy1"); + + Assertion assertion1 = new Assertion(); + assertion1.setAction("assume_role"); + assertion1.setEffect(AssertionEffect.ALLOW); + assertion1.setResource("testDomain.roleA"); + assertion1.setRole("testDomain.role.role1"); + + Assertion assertion2 = new Assertion(); + assertion2.setAction("read"); + assertion2.setEffect(AssertionEffect.ALLOW); + assertion2.setResource("testDomain.data:*"); + assertion2.setRole("testDomain.role.role1"); + + List assertList = new ArrayList(); + assertList.add(assertion1); + assertList.add(assertion2); + + policy.setAssertions(assertList); + + DataCache cache = new DataCache(); + cache.processRole(role1); + cache.processRole(role2); + cache.processRole(role3); + HashMap roleList = new HashMap<>(); + roleList.put(role1.getName(), role1); + roleList.put(role2.getName(), role2); + roleList.put(role3.getName(), role3); + cache.processPolicy(domain.getName(), policy, roleList); + + Set set1 = cache.getMemberRoleSet("user_domain.user1"); + assertNotNull(set1); + assertTrue(set1.contains("testDomain.role.role1")); + assertTrue(set1.contains("testDomain.roleA")); + assertEquals(set1.size(), 2); + + Set set2 = cache.getMemberRoleSet("user_domain.user2"); + assertNotNull(set2); + assertTrue(set2.contains("testDomain.role.role1")); + assertTrue(set2.contains("testDomain.role.role2")); + assertTrue(set2.contains("testDomain.roleA")); + assertEquals(set2.size(), 3); + + Set set3 = cache.getMemberRoleSet("user_domain.user3"); + assertNotNull(set3); + assertTrue(set3.contains("testDomain.role.role3")); + assertEquals(set3.size(), 1); + } + + @Test + public void testPolicyNoRoleProcessed() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + Role role1 = new Role(); + role1.setName("testDomain.role.role1"); + + List members1 = new ArrayList(); + members1.add("user_domain.user1"); + role1.setMembers(members1); + + Policy policy = new Policy(); + policy.setName("testDomain.policy.policy1"); + + Assertion assertion1 = new Assertion(); + assertion1.setAction("assume_role"); + assertion1.setEffect(AssertionEffect.ALLOW); + assertion1.setResource("testDomain.roleA"); + assertion1.setRole("testDomain.role.role1"); + + List assertList = new ArrayList(); + assertList.add(assertion1); + + policy.setAssertions(assertList); + + DataCache cache = new DataCache(); + HashMap roleList = new HashMap<>(); + roleList.put(role1.getName(), role1); + cache.processPolicy(domain.getName(), policy, roleList); + + Set set1 = cache.getMemberRoleSet("user_domain.user1"); + assertNotNull(set1); + assertTrue(set1.contains("testDomain.roleA")); + assertEquals(set1.size(), 1); + + Set set2 = cache.getMemberRoleSet("user_domain.user2"); + assertNull(set2); + } + + @Test + public void testPolicyWithNoAssertions() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + Role role1 = new Role(); + role1.setName("testDomain.role.role1"); + + List members1 = new ArrayList(); + members1.add("user_domain.user1"); + members1.add("user_domain.user2"); + role1.setMembers(members1); + + Role role2 = new Role(); + role2.setName("testDomain.role.role2"); + + List members2 = new ArrayList(); + members2.add("user_domain.user2"); + role2.setMembers(members2); + + Role role3 = new Role(); + role3.setName("testDomain.role.role3"); + + List members3 = new ArrayList(); + members3.add("user_domain.user3"); + role3.setMembers(members3); + + Policy policy = new Policy(); + policy.setName("testDomain.policy.policy1"); + + HashMap roleList = new HashMap<>(); + roleList.put(role1.getName(), role1); + roleList.put(role2.getName(), role2); + roleList.put(role3.getName(), role3); + + DataCache cache = new DataCache(); + cache.processRole(role1); + cache.processRole(role2); + cache.processRole(role3); + cache.processPolicy(domain.getName(), policy, roleList); + + Set set1 = cache.getMemberRoleSet("user_domain.user1"); + assertNotNull(set1); + assertTrue(set1.contains("testDomain.role.role1")); + assertEquals(set1.size(), 1); + + Set set2 = cache.getMemberRoleSet("user_domain.user2"); + assertNotNull(set2); + assertTrue(set2.contains("testDomain.role.role1")); + assertTrue(set2.contains("testDomain.role.role2")); + assertEquals(set2.size(), 2); + + Set set3 = cache.getMemberRoleSet("user_domain.user3"); + assertNotNull(set3); + assertTrue(set3.contains("testDomain.role.role3")); + assertEquals(set3.size(), 1); + } + + @Test + public void testPolicyWithInvalidAssertionRole() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + Role role1 = new Role(); + role1.setName("testDomain.role.role1"); + + List members1 = new ArrayList(); + members1.add("user_domain.user1"); + members1.add("user_domain.user2"); + role1.setMembers(members1); + + Role role2 = new Role(); + role2.setName("testDomain.role.role2"); + + List members2 = new ArrayList(); + members2.add("user_domain.user2"); + role2.setMembers(members2); + + Role role3 = new Role(); + role3.setName("testDomain.role.role3"); + + List members3 = new ArrayList(); + members3.add("user_domain.user3"); + role3.setMembers(members3); + + Policy policy = new Policy(); + policy.setName("testDomain.policy.policy1"); + + Assertion assertion = new Assertion(); + assertion.setAction("assume_role"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("testDomain.role"); + assertion.setRole("testDomain.role.Invalid"); + + List assertList = new ArrayList(); + assertList.add(assertion); + + policy.setAssertions(assertList); + + HashMap roleList = new HashMap<>(); + roleList.put(role1.getName(), role1); + roleList.put(role2.getName(), role2); + roleList.put(role3.getName(), role3); + + DataCache cache = new DataCache(); + cache.processRole(role1); + cache.processRole(role2); + cache.processRole(role3); + cache.processPolicy(domain.getName(), policy, roleList); + + Set set1 = cache.getMemberRoleSet("user_domain.user1"); + assertNotNull(set1); + assertTrue(set1.contains("testDomain.role.role1")); + assertEquals(set1.size(), 1); + + Set set2 = cache.getMemberRoleSet("user_domain.user2"); + assertNotNull(set2); + assertTrue(set2.contains("testDomain.role.role1")); + assertTrue(set2.contains("testDomain.role.role2")); + assertEquals(set2.size(), 2); + + Set set3 = cache.getMemberRoleSet("user_domain.user3"); + assertNotNull(set3); + assertTrue(set3.contains("testDomain.role.role3")); + assertEquals(set3.size(), 1); + } + + @Test + public void testPolicyWithInvalidDomainNoRoles() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + Policy policy = new Policy(); + policy.setName("testDomain.policy.policy1"); + + Assertion assertion = new Assertion(); + assertion.setAction("assume_role"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("testDomain.role"); + assertion.setRole("testDomain.role.role1"); + + List assertList = new ArrayList(); + assertList.add(assertion); + + policy.setAssertions(assertList); + + HashMap roleList = new HashMap<>(); + + DataCache cache = new DataCache(); + cache.processPolicy(domain.getName(), policy, roleList); + + Set set1 = cache.getMemberRoleSet("user_domain.user1"); + assertNull(set1); + } + + @Test + public void testPolicyWithAssertionRoleNoMember() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + Role role1 = new Role(); + role1.setName("testDomain.role.role1"); + + Role role2 = new Role(); + role2.setName("testDomain.role.role2"); + + List members2 = new ArrayList(); + members2.add("user_domain.user2"); + role2.setMembers(members2); + + Role role3 = new Role(); + role3.setName("testDomain.role.role3"); + + List members3 = new ArrayList(); + members3.add("user_domain.user3"); + role3.setMembers(members3); + + Policy policy = new Policy(); + policy.setName("testDomain.policy.policy1"); + + Assertion assertion = new Assertion(); + assertion.setAction("assume_role"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("testDomain.roleA"); + assertion.setRole("testDomain.role.role1"); + + List assertList = new ArrayList(); + assertList.add(assertion); + + policy.setAssertions(assertList); + + HashMap roleList = new HashMap<>(); + roleList.put(role1.getName(), role1); + roleList.put(role2.getName(), role2); + roleList.put(role3.getName(), role3); + + DataCache cache = new DataCache(); + cache.processRole(role1); + cache.processRole(role2); + cache.processRole(role3); + cache.processPolicy(domain.getName(), policy, roleList); + + Set set1 = cache.getMemberRoleSet("user_domain.user1"); + assertNull(set1); + + Set set2 = cache.getMemberRoleSet("user_domain.user2"); + assertNotNull(set2); + assertTrue(set2.contains("testDomain.role.role2")); + assertEquals(set2.size(), 1); + + Set set3 = cache.getMemberRoleSet("user_domain.user3"); + assertNotNull(set3); + assertTrue(set3.contains("testDomain.role.role3")); + assertEquals(set3.size(), 1); + } + + @Test + public void testSingleHostSingleService() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("testDomain.storage"); + List hosts = new ArrayList<>(); + hosts.add("host1"); + service.setHosts(hosts); + + DataCache cache = new DataCache(); + cache.processServiceIdentity(service); + + Map> hostMap = cache.getHostMap(); + assertEquals(hostMap.size(), 1); + assertTrue(hostMap.containsKey("host1")); + + Set set = hostMap.get("host1"); + assertEquals(set.size(), 1); + assertTrue(set.contains("testDomain.storage")); + } + + @Test + public void testMultipleHostsSingleService() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("testDomain.storage"); + List hosts = new ArrayList<>(); + hosts.add("host1"); + hosts.add("host2"); + service.setHosts(hosts); + + DataCache cache = new DataCache(); + cache.processServiceIdentity(service); + + Map> hostMap = cache.getHostMap(); + assertEquals(hostMap.size(), 2); + assertTrue(hostMap.containsKey("host1")); + + Set set = hostMap.get("host1"); + assertEquals(set.size(), 1); + assertTrue(set.contains("testDomain.storage")); + + assertTrue(hostMap.containsKey("host2")); + + set = hostMap.get("host2"); + assertEquals(set.size(), 1); + assertTrue(set.contains("testDomain.storage")); + } + + @Test + public void testMultipleHostsSkipDuplicate() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("testDomain.storage"); + List hosts = new ArrayList<>(); + hosts.add("host1"); + hosts.add("host2"); + hosts.add("host1"); + service.setHosts(hosts); + + DataCache cache = new DataCache(); + cache.processServiceIdentity(service); + + Map> hostMap = cache.getHostMap(); + assertEquals(hostMap.size(), 2); + assertTrue(hostMap.containsKey("host1")); + + Set set = hostMap.get("host1"); + assertEquals(set.size(), 1); + assertTrue(set.contains("testDomain.storage")); + + assertTrue(hostMap.containsKey("host2")); + + set = hostMap.get("host2"); + assertEquals(set.size(), 1); + assertTrue(set.contains("testDomain.storage")); + } + + @Test + public void testSingleHostMultipleServices() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + ServiceIdentity service1 = new ServiceIdentity(); + service1.setName("testDomain.storage1"); + List hosts1 = new ArrayList<>(); + hosts1.add("host1"); + service1.setHosts(hosts1); + + ServiceIdentity service2 = new ServiceIdentity(); + service2.setName("testDomain.storage2"); + List hosts2 = new ArrayList<>(); + hosts2.add("host1"); + service2.setHosts(hosts2); + + DataCache cache = new DataCache(); + cache.processServiceIdentity(service1); + cache.processServiceIdentity(service2); + + Map> hostMap = cache.getHostMap(); + assertEquals(hostMap.size(), 1); + assertTrue(hostMap.containsKey("host1")); + + Set set = hostMap.get("host1"); + assertEquals(set.size(), 2); + assertTrue(set.contains("testDomain.storage1")); + assertTrue(set.contains("testDomain.storage2")); + } + + @Test + public void testMultipleHostsMultipleServices() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + ServiceIdentity service1 = new ServiceIdentity(); + service1.setName("testDomain.storage1"); + List hosts1 = new ArrayList<>(); + hosts1.add("host1"); + hosts1.add("host2"); + service1.setHosts(hosts1); + + ServiceIdentity service2 = new ServiceIdentity(); + service2.setName("testDomain.storage2"); + List hosts2 = new ArrayList<>(); + hosts2.add("host1"); + hosts2.add("host3"); + service2.setHosts(hosts2); + + DataCache cache = new DataCache(); + cache.processServiceIdentity(service1); + cache.processServiceIdentity(service2); + + Map> hostMap = cache.getHostMap(); + assertEquals(hostMap.size(), 3); + assertTrue(hostMap.containsKey("host1")); + assertTrue(hostMap.containsKey("host2")); + assertTrue(hostMap.containsKey("host3")); + + Set set = hostMap.get("host1"); + assertEquals(set.size(), 2); + assertTrue(set.contains("testDomain.storage1")); + assertTrue(set.contains("testDomain.storage2")); + + set = hostMap.get("host2"); + assertEquals(set.size(), 1); + assertTrue(set.contains("testDomain.storage1")); + + set = hostMap.get("host3"); + assertEquals(set.size(), 1); + assertTrue(set.contains("testDomain.storage2")); + } + + @Test + public void testPublicKeysMultipleVersionFormat() { + + Domain domain = new Domain(); + domain.setName("testDomain"); + + ServiceIdentity service1 = new ServiceIdentity(); + service1.setName("testDomain.storage1"); + com.yahoo.athenz.zms.PublicKeyEntry keyEntry0 = new com.yahoo.athenz.zms.PublicKeyEntry(); + keyEntry0.setId("0"); + keyEntry0.setKey(ZTS_Y64_CERT0); + List listKeys1 = new ArrayList<>(); + listKeys1.add(keyEntry0); + service1.setPublicKeys(listKeys1); + + ServiceIdentity service2 = new ServiceIdentity(); + service2.setName("testDomain.storage2"); + + com.yahoo.athenz.zms.PublicKeyEntry keyEntry1 = new com.yahoo.athenz.zms.PublicKeyEntry(); + keyEntry1.setId("0"); + keyEntry1.setKey(ZTS_Y64_CERT1); + + com.yahoo.athenz.zms.PublicKeyEntry keyEntry3 = new com.yahoo.athenz.zms.PublicKeyEntry(); + keyEntry3.setId("3"); + keyEntry3.setKey(ZTS_Y64_CERT2); + + com.yahoo.athenz.zms.PublicKeyEntry keyEntry4 = new com.yahoo.athenz.zms.PublicKeyEntry(); + keyEntry4.setId("4"); + keyEntry4.setKey(ZTS_Y64_CERT3); + + List listKeys = new ArrayList<>(); + listKeys.add(keyEntry1); + listKeys.add(keyEntry3); + listKeys.add(keyEntry4); + + service2.setPublicKeys(listKeys); + + ServiceIdentity service3 = new ServiceIdentity(); + service3.setName("testDomain.storage3"); + + DataCache cache = new DataCache(); + cache.processServiceIdentity(service1); + cache.processServiceIdentity(service2); + cache.processServiceIdentity(service3); + + Map publicKeyMap = cache.getPublicKeyMap(); + assertEquals(publicKeyMap.size(), 4); + assertEquals(publicKeyMap.get("testDomain.storage1_0"), ZTS_PEM_CERT0); + assertEquals(publicKeyMap.get("testDomain.storage2_0"), ZTS_PEM_CERT1); + assertEquals(publicKeyMap.get("testDomain.storage2_3"), ZTS_PEM_CERT2); + assertEquals(publicKeyMap.get("testDomain.storage2_4"), ZTS_PEM_CERT3); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/MockCertSigner.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/MockCertSigner.java new file mode 100644 index 00000000000..87a17cb35ac --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/MockCertSigner.java @@ -0,0 +1,51 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert; + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import org.bouncycastle.pkcs.PKCS10CertificationRequest; + +import com.yahoo.athenz.auth.util.Crypto; + +public class MockCertSigner implements CertSigner { + + X509Certificate caCertificate = null; + PrivateKey caPrivateKey = null; + int certValidityTime = 30 * 24 * 60; // in minutes, default = 30 days + + public MockCertSigner(PrivateKey caPrivateKey, X509Certificate caCertificate) { + this.caCertificate = caCertificate; + this.caPrivateKey = caPrivateKey; + } + + @Override + public String generateX509Certificate(String csr) { + PKCS10CertificationRequest certReq = Crypto.getPKCS10CertRequest(csr); + X509Certificate cert = Crypto.generateX509Certificate(certReq, caPrivateKey, caCertificate, certValidityTime, false); + return Crypto.x509CertificateToPem(cert); + } + + @Override + public String getCACertificate() { + return Crypto.x509CertificateToPem(caCertificate); + } + + @Override + public void close() { + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/MockCertSignerFactory.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/MockCertSignerFactory.java new file mode 100644 index 00000000000..674c5ce0d85 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/MockCertSignerFactory.java @@ -0,0 +1,38 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert; + +import org.eclipse.jetty.client.HttpClient; + +import com.yahoo.athenz.auth.util.Crypto; + +import java.io.File; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +public class MockCertSignerFactory implements CertSignerFactory { + + @Override + public CertSigner create(HttpClient httpClient) { + File caCert = new File("src/test/resources/valid_cn_x509.cert"); + X509Certificate caCertificate = Crypto.loadX509Certificate(caCert); + + File caKey = new File("src/test/resources/private_encrypted.key"); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(caKey, "athenz"); + + return new MockCertSigner(caPrivateKey, caCertificate); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/X509CertSignObjectTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/X509CertSignObjectTest.java new file mode 100644 index 00000000000..56c699fa646 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/X509CertSignObjectTest.java @@ -0,0 +1,42 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert; + +import static org.testng.Assert.*; + +import org.testng.annotations.Test; + +import com.yahoo.rdl.JSON; + +public class X509CertSignObjectTest { + + @Test + public void testX509CertSignObject() { + + X509CertSignObject cert = new X509CertSignObject(); + assertNull(cert.getPem()); + + cert.setPem("pem-value"); + assertEquals(cert.getPem(), "pem-value"); + } + + @Test + public void testX509CertSignObjectStruct() { + + X509CertSignObject cert = JSON.fromString("{\"pem\":\"pem-value\"}", X509CertSignObject.class); + assertEquals(cert.getPem(), "pem-value"); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/impl/YCertSignerTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/impl/YCertSignerTest.java new file mode 100644 index 00000000000..6f1eeb0ca4d --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/cert/impl/YCertSignerTest.java @@ -0,0 +1,257 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.cert.impl; + +import static org.testng.Assert.*; + +import java.util.concurrent.TimeoutException; + +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.cert.impl.YCertSigner; +import com.yahoo.athenz.zts.cert.impl.YCertSignerFactory; +import com.yahoo.athenz.zts.utils.ZTSUtils; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +public class YCertSignerTest { + + @Test + public void testYCertSignerFactory() { + YCertSignerFactory certFactory = new YCertSignerFactory(); + assertNotNull(certFactory); + + HttpClient httpClient = new HttpClient(ZTSUtils.createSSLContextObject(new String[] {"TLSv1.2"})); + httpClient.setFollowRedirects(false); + + CertSigner certSigner = certFactory.create(httpClient); + assertNotNull(certSigner); + + certSigner.close(); + } + + @Test + public void testGenerateX509CertificateException() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + Request request = Mockito.mock(Request.class); + Mockito.when(httpClient.POST("https://localhost:443/certsign/v2/x509")).thenReturn(request); + Mockito.when(request.send()).thenThrow(new TimeoutException()); + + assertNull(certSigner.generateX509Certificate("csr")); + } + + @Test + public void testGenerateX509CertificateInvalidStatus() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + Request request = Mockito.mock(Request.class); + Mockito.when(httpClient.POST("https://localhost:443/certsign/v2/x509")).thenReturn(request); + + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(request.send()).thenReturn(response); + Mockito.when(response.getStatus()).thenReturn(400); + + assertNull(certSigner.generateX509Certificate("csr")); + } + + @Test + public void testGenerateX509CertificateResponseNull() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + Request request = Mockito.mock(Request.class); + Mockito.when(httpClient.POST("https://localhost:443/certsign/v2/x509")).thenReturn(request); + + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(request.send()).thenReturn(response); + Mockito.when(response.getStatus()).thenReturn(201); + Mockito.when(response.getContentAsString()).thenReturn(null); + + assertNull(certSigner.generateX509Certificate("csr")); + } + + @Test + public void testGenerateX509CertificateResponseEmpty() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + Request request = Mockito.mock(Request.class); + Mockito.when(httpClient.POST("https://localhost:443/certsign/v2/x509")).thenReturn(request); + + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(request.send()).thenReturn(response); + Mockito.when(response.getStatus()).thenReturn(201); + Mockito.when(response.getContentAsString()).thenReturn(""); + + assertNull(certSigner.generateX509Certificate("csr")); + } + + @Test + public void testGenerateX509Certificate() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + Request request = Mockito.mock(Request.class); + Mockito.when(httpClient.POST("https://localhost:443/certsign/v2/x509")).thenReturn(request); + + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(request.send()).thenReturn(response); + Mockito.when(response.getStatus()).thenReturn(201); + Mockito.when(response.getContentAsString()).thenReturn("{\"pem\": \"pem-value\"}"); + + String pem = certSigner.generateX509Certificate("csr"); + assertEquals(pem, "pem-value"); + } + + @Test + public void testGenerateX509CertificateInvalidData() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + Request request = Mockito.mock(Request.class); + Mockito.when(httpClient.POST("https://localhost:443/certsign/v2/x509")).thenReturn(request); + + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(request.send()).thenReturn(response); + Mockito.when(response.getStatus()).thenReturn(201); + Mockito.when(response.getContentAsString()).thenReturn("{\"pem2\": \"pem-value\"}"); + + assertNull(certSigner.generateX509Certificate("csr")); + + Mockito.when(response.getContentAsString()).thenReturn("invalid-json"); + assertNull(certSigner.generateX509Certificate("csr")); + } + + @Test + public void testGetCACertificateException() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + Mockito.when(httpClient.GET("https://localhost:443/certsign/v2/x509")).thenThrow(new TimeoutException()); + + assertNull(certSigner.getCACertificate()); + } + + @Test + public void testGetCACertificateInvalidStatus() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(httpClient.GET("https://localhost:443/certsign/v2/x509")).thenReturn(response); + Mockito.when(response.getStatus()).thenReturn(400); + + assertNull(certSigner.getCACertificate()); + } + + @Test + public void testGetCACertificateResponseNull() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(httpClient.GET("https://localhost:443/certsign/v2/x509")).thenReturn(response); + Mockito.when(response.getStatus()).thenReturn(200); + Mockito.when(response.getContentAsString()).thenReturn(null); + + assertNull(certSigner.getCACertificate()); + } + + @Test + public void testGetCACertificateResponseEmpty() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(httpClient.GET("https://localhost:443/certsign/v2/x509")).thenReturn(response); + Mockito.when(response.getStatus()).thenReturn(200); + Mockito.when(response.getContentAsString()).thenReturn(""); + + assertNull(certSigner.getCACertificate()); + } + + @Test + public void testGetCACertificate() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(httpClient.GET("https://localhost:443/certsign/v2/x509")).thenReturn(response); + Mockito.when(response.getStatus()).thenReturn(200); + Mockito.when(response.getContentAsString()).thenReturn("{\"pem\": \"pem-value\"}"); + + String pem = certSigner.getCACertificate(); + assertEquals(pem, "pem-value"); + } + + @Test + public void testGetCACertificateInvalidData() throws Exception { + + HttpClient httpClient = Mockito.mock(HttpClient.class); + + YCertSignerFactory certFactory = new YCertSignerFactory(); + YCertSigner certSigner = (YCertSigner) certFactory.create(httpClient); + + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(httpClient.GET("https://localhost:443/certsign/v2/x509")).thenReturn(response); + Mockito.when(response.getStatus()).thenReturn(200); + Mockito.when(response.getContentAsString()).thenReturn("{\"pem2\": \"pem-value\"}"); + + assertNull(certSigner.getCACertificate()); + + Mockito.when(response.getContentAsString()).thenReturn("invalid-json"); + assertNull(certSigner.getCACertificate()); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStoreTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStoreTest.java new file mode 100644 index 00000000000..9cbe863fb36 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/pkey/file/FilePrivateKeyStoreTest.java @@ -0,0 +1,145 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.pkey.file; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.fail; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.zts.ResourceException; +import com.yahoo.athenz.zts.ZTSConsts; +import com.yahoo.athenz.zts.pkey.PrivateKeyStore; + +public class FilePrivateKeyStoreTest { + + @Test + public void testCreateStore() { + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + assertNotNull(store); + } + + @Test + public void testGetHostPrivateKeyExist() { + + // set default filepath property + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY, "src/test/resources/zts_private.pem"); + + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + StringBuilder sbuilder = new StringBuilder(); + + assertNotNull(store.getHostPrivateKey(sbuilder)); + } + + @Test + public void testGetHostPrivateKeyPkeyNotExist() { + // set default filepath property + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY, "src/test/resources/unknown.pem"); + + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + StringBuilder sbuilder = new StringBuilder(); + + try { + store.getHostPrivateKey(sbuilder); + } catch (RuntimeException ex) { + assertNotNull(ex.getMessage()); + } + + // fix property + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY, "src/test/resources/zts_private.pem"); + + } + + @Test + public void testGetHostPrivateKeyInvalidFormat() { + // set default filepath property + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY, "src/test/resources/test_public.v1"); + + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + StringBuilder sbuilder = new StringBuilder(); + + try { + store.getHostPrivateKey(sbuilder); + } catch (ResourceException ex) { + assertNotNull(ex.getCode()); + } + + // fix property + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY, "src/test/resources/zts_private.pem"); + + } + + @Test + public void testGetPrivateKeyExist() { + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + assertNotNull(store.getPrivateKey("src/test/resources/test_private", 1)); + + } + + @Test + public void testGetPrivateKeyInvalidFormat() { + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + try { + store.getPrivateKey("src/test/resources/test_public", 1); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 500); + } + } + + @Test + public void testGetPublicKeyExist() { + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + assertNotNull(store.getPublicKey("src/test/resources/test_public", 1)); + } + + @Test + public void testGetPublicKeyInvalidFormat() { + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + // not validate pubkey format + assertNotNull(store.getPublicKey("src/test/resources/test_private", 1)); + } + + @Test + public void testGetPublicKeyNotExist() { + FilePrivateKeyStoreFactory factory = new FilePrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + + try { + store.getPublicKey("src/test/resources/test_public", 2); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 500); + } + } + +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStoreTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStoreTest.java new file mode 100644 index 00000000000..66b885919ca --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/pkey/hsm/HSMPrivateKeyStoreTest.java @@ -0,0 +1,46 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.pkey.hsm; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; + +import java.security.PrivateKey; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.zts.pkey.PrivateKeyStore; +import com.yahoo.athenz.zts.pkey.hsm.HSMPrivateKeyStore; +import com.yahoo.athenz.zts.pkey.hsm.HSMPrivateKeyStoreFactory; + +public class HSMPrivateKeyStoreTest { + + @Test + public void testCreateStore() { + HSMPrivateKeyStoreFactory factory = new HSMPrivateKeyStoreFactory(); + PrivateKeyStore store = factory.create("localhost"); + assertNotNull(store); + } + + @Test + public void testGetPrivateKey() { + + HSMPrivateKeyStore store = new HSMPrivateKeyStore(); + StringBuilder privateKeyId = new StringBuilder(256); + PrivateKey pkey = store.getHostPrivateKey(privateKeyId); + assertNull(pkey); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/store/CloudStoreTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/CloudStoreTest.java new file mode 100644 index 00000000000..a8b6a7a6b4b --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/CloudStoreTest.java @@ -0,0 +1,928 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store; + +import static org.testng.Assert.*; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import com.amazonaws.auth.BasicSessionCredentials; +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; +import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; +import com.amazonaws.services.securitytoken.model.AssumeRoleResult; +import com.amazonaws.services.securitytoken.model.Credentials; +import com.amazonaws.services.securitytoken.model.GetCallerIdentityResult; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.zts.AWSInstanceInformation; +import com.yahoo.athenz.zts.AWSTemporaryCredentials; +import com.yahoo.athenz.zts.Identity; +import com.yahoo.athenz.zts.ResourceException; +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.cert.MockCertSigner; +import com.yahoo.athenz.zts.store.CloudStore; + +public class CloudStoreTest { + + public final static String AWS_INSTANCE_DOCUMENT = "{\n" + + " \"devpayProductCodes\" : null,\n" + + " \"availabilityZone\" : \"us-west-2a\",\n" + + " \"privateIp\" : \"10.10.10.10\",\n" + + " \"version\" : \"2010-08-31\",\n" + + " \"instanceId\" : \"i-056921225f1fbb47a\",\n" + + " \"billingProducts\" : null,\n" + + " \"instanceType\" : \"t2.micro\",\n" + + " \"accountId\" : \"111111111111\",\n" + + " \"pendingTime\" : \"2016-04-26T05:37:23Z\",\n" + + " \"imageId\" : \"ami-c229c0a2\",\n" + + " \"architecture\" : \"x86_64\",\n" + + " \"kernelId\" : null,\n" + + " \"ramdiskId\" : null,\n" + + " \"region\" : \"us-west-2\"\n" + + "}"; + + public final static String AWS_IAM_ROLE_INFO = "{\n" + + "\"Code\" : \"Success\",\n" + + "\"LastUpdated\" : \"2016-04-26T05:37:04Z\",\n" + + "\"InstanceProfileArn\" : \"arn:aws:iam::111111111111:instance-profile/athenz.zts,athenz\",\n" + + "\"InstanceProfileId\" : \"AIPAJAVNLUGEWFWTIDPRA\"\n" + + "}"; + + @Test + public void testGetS3ClientNullCreds() { + CloudStore store = new CloudStore(null); + store.awsEnabled = true; + store.credentials = null; + try { + store.getS3Client(); + fail(); + } catch (ResourceException ex) { + assertEquals(500, ex.getCode()); + } + store.close(); + } + + @Test + public void testGetS3ClientAWSNotEnabled() { + CloudStore store = new CloudStore(null); + store.credentials = null; + try { + store.getS3Client(); + fail(); + } catch (ResourceException ex) { + assertEquals(500, ex.getCode()); + } + store.close(); + } + + @Test + public void testGetS3Client() { + + CloudStore store = new CloudStore(null); + store.credentials = new BasicSessionCredentials("accessKey", "secretKey", "token"); + store.awsEnabled = true; + assertNotNull(store.getS3Client()); + + store.awsRegion = "us-west-2"; + assertNotNull(store.getS3Client()); + store.close(); + } + + @Test + public void testGetTokenServiceClient() { + CloudStore store = new CloudStore(null); + store.credentials = new BasicSessionCredentials("accessKey", "secretKey", "token"); + store.awsEnabled = true; + assertNotNull(store.getTokenServiceClient()); + store.close(); + } + + @Test + public void testUpdateAccountUpdate() { + + CloudStore store = new CloudStore(null); + assertNull(store.getAWSAccount("iaas")); + + // set the account to 1234 + + store.updateAccount("iaas", "1234"); + assertEquals("1234", store.getAWSAccount("iaas")); + + // update the account value + + store.updateAccount("iaas", "1235"); + assertEquals("1235", store.getAWSAccount("iaas")); + store.close(); + } + + @Test + public void testUpdateAccountDelete() { + + CloudStore store = new CloudStore(null); + + // set the account to 1234 + + store.updateAccount("iaas", "1234"); + assertEquals("1234", store.getAWSAccount("iaas")); + + // delete the account with null + + store.updateAccount("iaas", null); + assertNull(store.getAWSAccount("iaas")); + + // update the account value + + store.updateAccount("iaas", "1235"); + assertEquals("1235", store.getAWSAccount("iaas")); + + // delete the account with empty string + + store.updateAccount("iaas", ""); + assertNull(store.getAWSAccount("iaas")); + store.close(); + } + + @Test + public void testGetAssumeRoleRequest() { + + CloudStore store = new CloudStore(null); + AssumeRoleRequest req = store.getAssumeRoleRequest("1234", "admin", "sys.auth.zts"); + assertEquals("arn:aws:iam::1234:role/admin", req.getRoleArn()); + assertEquals("sys.auth.zts", req.getRoleSessionName()); + store.close(); + } + + @Test + public void testParseInstanceInfo() { + CloudStore store = new CloudStore(null); + assertTrue(store.parseInstanceInfo(AWS_INSTANCE_DOCUMENT)); + assertEquals(store.awsAccount, "111111111111"); + assertEquals(store.awsRegion, "us-west-2"); + store.close(); + } + + @Test + public void testParseInstanceInfoInvalid() { + + CloudStore store = new CloudStore(null); + assertFalse(store.parseInstanceInfo("some_invalid_doc")); + store.close(); + } + + @Test + public void testParseInstanceInfoRegion() { + + // first this should fail since we have no region + // override and the document has no region + + CloudStore store = new CloudStore(null); + assertFalse(store.parseInstanceInfo("{\"accountId\":\"012345678901\"}")); + + // now we're going to use the same doc with override + + System.setProperty(CloudStore.ZTS_PROP_AWS_REGION_NAME, "us-west-3"); + store.close(); + + store = new CloudStore(null); + assertTrue(store.parseInstanceInfo("{\"accountId\":\"012345678901\"}")); + assertEquals(store.awsAccount, "012345678901"); + assertEquals(store.awsRegion, "us-west-3"); + System.clearProperty(CloudStore.ZTS_PROP_AWS_REGION_NAME); + store.close(); + } + + @Test + public void testParseIamRoleInfoInvalid() { + + CloudStore store = new CloudStore(null); + assertFalse(store.parseIamRoleInfo("some_invalid_doc")); + store.close(); + } + + @Test + public void testParseIamRoleInfoMissingInstanceProfile() { + + CloudStore store = new CloudStore(null); + assertFalse(store.parseIamRoleInfo("{\"accountId\":\"012345678901\"}")); + assertFalse(store.parseIamRoleInfo("{\"accountId\":\"012345678901\",\"InstanceProfileArn\":\"\"}")); + store.close(); + } + + @Test + public void testParseIamRoleInfoInvalidInstanceProfile() { + + CloudStore store = new CloudStore(null); + assertFalse(store.parseIamRoleInfo("{\"accountId\":\"012345678901\"}")); + assertFalse(store.parseIamRoleInfo("{\"accountId\":\"012345678901\",\"InstanceProfileArn\":\"invalid\"}")); + store.close(); + } + + @Test + public void testParseIamRoleInfo() { + CloudStore store = new CloudStore(null); + assertTrue(store.parseIamRoleInfo(AWS_IAM_ROLE_INFO)); + assertEquals(store.awsAccount, "111111111111"); + assertEquals(store.awsCloud, "athenz"); + assertEquals(store.awsDomain, "athenz"); + assertEquals(store.awsRole, "athenz.zts"); + assertEquals(store.awsService, "zts"); + assertEquals(store.awsProfile, "athenz.zts,athenz"); + store.close(); + } + + @Test + public void testParseInstanceProfileArn() { + + CloudStore store = new CloudStore(null); + assertTrue(store.parseInstanceProfileArn("arn:aws:iam::111111111111:instance-profile/athenz.zts,athenz")); + assertEquals(store.awsAccount, "111111111111"); + assertEquals(store.awsCloud, "athenz"); + assertEquals(store.awsDomain, "athenz"); + assertEquals(store.awsRole, "athenz.zts"); + assertEquals(store.awsService, "zts"); + assertEquals(store.awsProfile, "athenz.zts,athenz"); + store.close(); + } + + @Test + public void testParseInstanceProfileArnInvalidPrefix() { + + CloudStore store = new CloudStore(null); + + // invalid starting prefix + + assertFalse(store.parseInstanceProfileArn("arn:aws:iam:111111111111:instance-profile/athenz.zts,athenz")); + assertFalse(store.parseInstanceProfileArn("arn:aws:iam2:111111111111:instance-profile/athenz.zts,athenz")); + assertFalse(store.parseInstanceProfileArn("instance-profile/athenz.zts,athenz")); + store.close(); + } + + @Test + public void testParseInstanceProfileArnInvalidProfile() { + + CloudStore store = new CloudStore(null); + + // missing instance-profile part + + assertFalse(store.parseInstanceProfileArn("arn:aws:iam::111111111111:instance-profile2/athenz.zts,athenz")); + assertFalse(store.parseInstanceProfileArn("arn:aws:iam::111111111111:instance/athenz.zts,athenz")); + store.close(); + } + + @Test + public void testParseInstanceProfileArnInvalidNoProfile() { + + CloudStore store = new CloudStore(null); + + // no profile name + + assertFalse(store.parseInstanceProfileArn("arn:aws:iam::111111111111:instance-profile/")); + store.close(); + } + + @Test + public void testParseInstanceProfileArnInvalidAccount() { + + CloudStore store = new CloudStore(null); + + // missing account id + + assertFalse(store.parseInstanceProfileArn("arn:aws:iam:::instance-profile/athenz.zts,athenz")); + store.close(); + } + + @Test + public void testParseInstanceProfileArnInvalidCloud() { + + CloudStore store = new CloudStore(null); + + // missing/invalid cloud name + + assertFalse(store.parseInstanceProfileArn("arn:aws:iam::111111111111:instance-profile/athenz.zts")); + assertFalse(store.parseInstanceProfileArn("arn:aws:iam::111111111111:instance-profile/athenz.zts,athenz,test")); + store.close(); + } + + @Test + public void testParseInstanceProfileArnInvalidDomain() { + + CloudStore store = new CloudStore(null); + + // invalid domain/service names + + assertFalse(store.parseInstanceProfileArn("arn:aws:iam::111111111111:instance-profile/iaas,athenz")); + assertFalse(store.parseInstanceProfileArn("arn:aws:iam::111111111111:instance-profile/iaas.,athenz")); + assertFalse(store.parseInstanceProfileArn("arn:aws:iam::111111111111:instance-profile/.iaas,athenz")); + store.close(); + } + + @SuppressWarnings("unchecked") + @Test + public void testGetMetaDataExceptions() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/exc1")).thenThrow(InterruptedException.class); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/exc2")).thenThrow(ExecutionException.class); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/exc3")).thenThrow(TimeoutException.class); + + assertNull(store.getMetaData("/exc1")); + assertNull(store.getMetaData("/exc2")); + assertNull(store.getMetaData("/exc3")); + store.close(); + } + + @Test + public void testGetMetaDataFailureStatus() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(response.getStatus()).thenReturn(404); + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/iam-info")).thenReturn(response); + + assertNull(store.getMetaData("/iam-info")); + store.close(); + } + + @Test + public void testGetMetaDataNullResponse() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(response.getStatus()).thenReturn(200); + Mockito.when(response.getContentAsString()).thenReturn(null); + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/iam-info")).thenReturn(response); + + assertNull(store.getMetaData("/iam-info")); + store.close(); + } + + @Test + public void testGetMetaDataEmptyResponse() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(response.getStatus()).thenReturn(200); + Mockito.when(response.getContentAsString()).thenReturn(""); + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/iam-info")).thenReturn(response); + + assertNull(store.getMetaData("/iam-info")); + store.close(); + } + + @Test + public void testGetMetaDataValidResponse() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(response.getStatus()).thenReturn(200); + Mockito.when(response.getContentAsString()).thenReturn("json-document"); + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/iam-info")).thenReturn(response); + + assertEquals(store.getMetaData("/iam-info"), "json-document"); + store.close(); + } + + @Test + public void testLoadBootMetaDataInvalidDocumentGet() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(response.getStatus()).thenReturn(404); + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/document")).thenReturn(response); + + assertFalse(store.loadBootMetaData()); + store.close(); + } + + @Test + public void testLoadBootMetaDataInvalidDocumentParse() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(response.getStatus()).thenReturn(200); + Mockito.when(response.getContentAsString()).thenReturn("{\"accountId\":\"012345678901\"}"); + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/document")).thenReturn(response); + + assertFalse(store.loadBootMetaData()); + store.close(); + } + + @Test + public void testLoadBootMetaDataInvalidDocumentException() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(response.getStatus()).thenReturn(200); + Mockito.when(response.getContentAsString()).thenReturn("json-document"); + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/document")).thenReturn(response); + + assertFalse(store.loadBootMetaData()); + store.close(); + } + + @Test + public void testLoadBootMetaDataInvalidSignature() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + ContentResponse responseDoc = Mockito.mock(ContentResponse.class); + Mockito.when(responseDoc.getStatus()).thenReturn(200); + Mockito.when(responseDoc.getContentAsString()).thenReturn(AWS_INSTANCE_DOCUMENT); + + ContentResponse responseSig = Mockito.mock(ContentResponse.class); + Mockito.when(responseSig.getStatus()).thenReturn(404); + + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/document")).thenReturn(responseDoc); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/pkcs7")).thenReturn(responseSig); + + assertFalse(store.loadBootMetaData()); + store.close(); + } + + @Test + public void testLoadBootMetaDataInvalidIamInfoGet() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + + ContentResponse responseDoc = Mockito.mock(ContentResponse.class); + Mockito.when(responseDoc.getStatus()).thenReturn(200); + Mockito.when(responseDoc.getContentAsString()).thenReturn(AWS_INSTANCE_DOCUMENT); + + ContentResponse responseSig = Mockito.mock(ContentResponse.class); + Mockito.when(responseSig.getStatus()).thenReturn(200); + Mockito.when(responseSig.getContentAsString()).thenReturn("pkcs7-signature"); + + ContentResponse responseInfo = Mockito.mock(ContentResponse.class); + Mockito.when(responseInfo.getStatus()).thenReturn(404); + + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/document")).thenReturn(responseDoc); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/pkcs7")).thenReturn(responseSig); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/meta-data/iam/info")).thenReturn(responseInfo); + + assertFalse(store.loadBootMetaData()); + store.close(); + } + + @Test + public void testLoadBootMetaDataInvalidIamInfoException() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + + ContentResponse responseDoc = Mockito.mock(ContentResponse.class); + Mockito.when(responseDoc.getStatus()).thenReturn(200); + Mockito.when(responseDoc.getContentAsString()).thenReturn(AWS_INSTANCE_DOCUMENT); + + ContentResponse responseSig = Mockito.mock(ContentResponse.class); + Mockito.when(responseSig.getStatus()).thenReturn(200); + Mockito.when(responseSig.getContentAsString()).thenReturn("pkcs7-signature"); + + ContentResponse responseInfo = Mockito.mock(ContentResponse.class); + Mockito.when(responseInfo.getStatus()).thenReturn(200); + Mockito.when(responseInfo.getContentAsString()).thenReturn("invalid-info"); + + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/document")).thenReturn(responseDoc); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/pkcs7")).thenReturn(responseSig); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/meta-data/iam/info")).thenReturn(responseInfo); + + assertFalse(store.loadBootMetaData()); + store.close(); + } + + @Test + public void testLoadBootMetaDataInvalidIamInfoParse() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + + ContentResponse responseDoc = Mockito.mock(ContentResponse.class); + Mockito.when(responseDoc.getStatus()).thenReturn(200); + Mockito.when(responseDoc.getContentAsString()).thenReturn(AWS_INSTANCE_DOCUMENT); + + ContentResponse responseSig = Mockito.mock(ContentResponse.class); + Mockito.when(responseSig.getStatus()).thenReturn(200); + Mockito.when(responseSig.getContentAsString()).thenReturn("pkcs7-signature"); + + ContentResponse responseInfo = Mockito.mock(ContentResponse.class); + Mockito.when(responseInfo.getStatus()).thenReturn(200); + Mockito.when(responseInfo.getContentAsString()).thenReturn("{\"accountId\":\"012345678901\",\"InstanceProfileArn\":\"invalid\"}"); + + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/document")).thenReturn(responseDoc); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/pkcs7")).thenReturn(responseSig); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/meta-data/iam/info")).thenReturn(responseInfo); + + assertFalse(store.loadBootMetaData()); + store.close(); + } + + @Test + public void testLoadBootMetaData() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + + ContentResponse responseDoc = Mockito.mock(ContentResponse.class); + Mockito.when(responseDoc.getStatus()).thenReturn(200); + Mockito.when(responseDoc.getContentAsString()).thenReturn(AWS_INSTANCE_DOCUMENT); + + ContentResponse responseSig = Mockito.mock(ContentResponse.class); + Mockito.when(responseSig.getStatus()).thenReturn(200); + Mockito.when(responseSig.getContentAsString()).thenReturn("pkcs7-signature"); + + ContentResponse responseInfo = Mockito.mock(ContentResponse.class); + Mockito.when(responseInfo.getStatus()).thenReturn(200); + Mockito.when(responseInfo.getContentAsString()).thenReturn(AWS_IAM_ROLE_INFO); + + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/document")).thenReturn(responseDoc); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/pkcs7")).thenReturn(responseSig); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/meta-data/iam/info")).thenReturn(responseInfo); + + assertTrue(store.loadBootMetaData()); + assertEquals(store.awsAccount, "111111111111"); + assertEquals(store.awsCloud, "athenz"); + assertEquals(store.awsDomain, "athenz"); + assertEquals(store.awsRole, "athenz.zts"); + assertEquals(store.awsService, "zts"); + assertEquals(store.awsProfile, "athenz.zts,athenz"); + assertEquals(store.awsRegion, "us-west-2"); + store.close(); + } + + @Test + public void testFetchRoleCredentialsNoRole() { + + CloudStore store = new CloudStore(null); + + store.awsRole = null; + assertFalse(store.fetchRoleCredentials()); + + store.awsRole = ""; + assertFalse(store.fetchRoleCredentials()); + store.close(); + } + + @Test + public void testFetchRoleCredentialsNoCreds() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + store.awsRole = "athenz.zts"; + + HttpClient httpClient = Mockito.mock(HttpClient.class); + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(response.getStatus()).thenReturn(404); + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/meta-data/iam/security-credentials/athenz.zts")).thenReturn(response); + + assertFalse(store.fetchRoleCredentials()); + store.close(); + } + + @Test + public void testFetchRoleCredentialInvalidCreds() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + store.awsRole = "athenz.zts"; + + HttpClient httpClient = Mockito.mock(HttpClient.class); + ContentResponse response = Mockito.mock(ContentResponse.class); + Mockito.when(response.getStatus()).thenReturn(200); + Mockito.when(response.getContentAsString()).thenReturn("invalid-creds"); + + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/meta-data/iam/security-credentials/athenz.zts")).thenReturn(response); + + assertFalse(store.fetchRoleCredentials()); + store.close(); + } + + @Test + public void testInitializeAwsSupportInvalidDocument() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + + ContentResponse responseDoc = Mockito.mock(ContentResponse.class); + Mockito.when(responseDoc.getStatus()).thenReturn(200); + Mockito.when(responseDoc.getContentAsString()).thenReturn("invalid-document"); + + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/document")).thenReturn(responseDoc); + + try { + store.awsEnabled = true; + store.initializeAwsSupport(); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 500); + } + store.close(); + } + + @Test + public void testInitializeAwsSupportInvalidCreds() throws InterruptedException, ExecutionException, TimeoutException { + + CloudStore store = new CloudStore(null); + HttpClient httpClient = Mockito.mock(HttpClient.class); + + ContentResponse responseDoc = Mockito.mock(ContentResponse.class); + Mockito.when(responseDoc.getStatus()).thenReturn(200); + Mockito.when(responseDoc.getContentAsString()).thenReturn(AWS_INSTANCE_DOCUMENT); + + ContentResponse responseSig = Mockito.mock(ContentResponse.class); + Mockito.when(responseSig.getStatus()).thenReturn(200); + Mockito.when(responseSig.getContentAsString()).thenReturn("pkcs7-signature"); + + ContentResponse responseInfo = Mockito.mock(ContentResponse.class); + Mockito.when(responseInfo.getStatus()).thenReturn(200); + Mockito.when(responseInfo.getContentAsString()).thenReturn(AWS_IAM_ROLE_INFO); + + ContentResponse responseCreds = Mockito.mock(ContentResponse.class); + Mockito.when(responseCreds.getStatus()).thenReturn(200); + Mockito.when(responseCreds.getContentAsString()).thenReturn("invalid-creds"); + + store.setHttpClient(httpClient); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/document")).thenReturn(responseDoc); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/dynamic/instance-identity/pkcs7")).thenReturn(responseSig); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/meta-data/iam/info")).thenReturn(responseInfo); + Mockito.when(httpClient.GET("http://169.254.169.254/latest/meta-data/iam/security-credentials/athenz.zts")).thenReturn(responseCreds); + + try { + store.awsEnabled = true; + store.initializeAwsSupport(); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 500); + } + store.close(); + } + + @Test + public void testGenerateIdentity() throws IOException { + + File caCert = new File("src/test/resources/valid_cn_x509.cert"); + X509Certificate caCertificate = Crypto.loadX509Certificate(caCert); + + File caKey = new File("src/test/resources/private_encrypted.key"); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(caKey, "athenz"); + CertSigner certSigner = new MockCertSigner(caPrivateKey, caCertificate); + + CloudStore cloudStore = new CloudStore(certSigner); + + Path path = Paths.get("src/test/resources/valid.csr"); + String certCsr = new String(Files.readAllBytes(path)); + + Identity identity = cloudStore.generateIdentity(certCsr, "athenz.syncer"); + assertNotNull(identity); + cloudStore.close(); + } + + @Test + public void testGenerateIdentityInvalidCsr() throws IOException { + + File caCert = new File("src/test/resources/valid_cn_x509.cert"); + X509Certificate caCertificate = Crypto.loadX509Certificate(caCert); + + File caKey = new File("src/test/resources/private_encrypted.key"); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(caKey, "athenz"); + CertSigner certSigner = new MockCertSigner(caPrivateKey, caCertificate); + + CloudStore cloudStore = new CloudStore(certSigner); + + Identity identity = cloudStore.generateIdentity("invalid-csr", "athenz.syncer"); + assertNull(identity); + cloudStore.close(); + } + + @Test + public void testGenerateIdentityMismatchCN() throws IOException { + + File caCert = new File("src/test/resources/valid_cn_x509.cert"); + X509Certificate caCertificate = Crypto.loadX509Certificate(caCert); + + File caKey = new File("src/test/resources/private_encrypted.key"); + PrivateKey caPrivateKey = Crypto.loadPrivateKey(caKey, "athenz"); + CertSigner certSigner = new MockCertSigner(caPrivateKey, caCertificate); + + CloudStore cloudStore = new CloudStore(certSigner); + + Path path = Paths.get("src/test/resources/valid.csr"); + String certCsr = new String(Files.readAllBytes(path)); + + Identity identity = cloudStore.generateIdentity(certCsr, "athenz.backup"); + assertNull(identity); + + identity = cloudStore.generateIdentity(certCsr, "athenz"); + assertNull(identity); + cloudStore.close(); + } + + @Test + public void testGetInstanceClient() { + AWSInstanceInformation info = new AWSInstanceInformation(); + info.setAccess("access"); + info.setSecret("secret"); + info.setToken("token"); + + CloudStore cloudStore = new CloudStore(null); + AWSSecurityTokenServiceClient client = cloudStore.getInstanceClient(info); + assertNotNull(client); + cloudStore.close(); + } + + @Test + public void testGetInstanceClientInvalidAccess() { + AWSInstanceInformation info = new AWSInstanceInformation(); + info.setAccess(""); + info.setSecret("secret"); + info.setToken("token"); + + CloudStore cloudStore = new CloudStore(null); + assertNull(cloudStore.getInstanceClient(info)); + + info = new AWSInstanceInformation(); + info.setSecret("secret"); + info.setToken("token"); + + assertNull(cloudStore.getInstanceClient(info)); + cloudStore.close(); + } + + @Test + public void testGetInstanceClientInvalidSecret() { + AWSInstanceInformation info = new AWSInstanceInformation(); + info.setAccess("access"); + info.setSecret(""); + info.setToken("token"); + + CloudStore cloudStore = new CloudStore(null); + assertNull(cloudStore.getInstanceClient(info)); + + info = new AWSInstanceInformation(); + info.setAccess("access"); + info.setToken("token"); + + assertNull(cloudStore.getInstanceClient(info)); + cloudStore.close(); + } + + @Test + public void testGetInstanceClientInvalidToken() { + AWSInstanceInformation info = new AWSInstanceInformation(); + info.setAccess("access"); + info.setSecret("secret"); + info.setToken(""); + + CloudStore cloudStore = new CloudStore(null); + assertNull(cloudStore.getInstanceClient(info)); + + info = new AWSInstanceInformation(); + info.setAccess("access"); + info.setSecret("secret"); + + assertNull(cloudStore.getInstanceClient(info)); + cloudStore.close(); + } + + @Test + public void testVerifyInstanceIdentityNullResult() { + MockCloudStore cloudStore = new MockCloudStore(); + cloudStore.setAssumeRoleResult(null); + AWSInstanceInformation info = new AWSInstanceInformation() + .setDomain("athenz") + .setService("zts") + .setAccount("12345"); + assertFalse(cloudStore.verifyInstanceIdentity(info)); + } + + @Test + public void testVerifyInstanceIdentityNullClient() { + MockCloudStore cloudStore = new MockCloudStore(); + cloudStore.setGetCallerIdentityResult(null); + cloudStore.setReturnNullClient(true); + AWSInstanceInformation info = new AWSInstanceInformation() + .setDomain("athenz") + .setService("zts") + .setAccount("12345"); + assertFalse(cloudStore.verifyInstanceIdentity(info)); + } + + @Test + public void testVerifyInstanceIdentityNullCreds() { + MockCloudStore cloudStore = new MockCloudStore(); + GetCallerIdentityResult mockResult = Mockito.mock(GetCallerIdentityResult.class); + Mockito.when(mockResult.getArn()).thenReturn(null); + cloudStore.setGetCallerIdentityResult(mockResult); + AWSInstanceInformation info = new AWSInstanceInformation() + .setDomain("athenz") + .setService("zts") + .setAccount("12345"); + assertFalse(cloudStore.verifyInstanceIdentity(info)); + } + + @Test + public void testVerifyInstanceIdentityMismatchCreds() { + MockCloudStore cloudStore = new MockCloudStore(); + GetCallerIdentityResult mockResult = Mockito.mock(GetCallerIdentityResult.class); + Mockito.when(mockResult.getArn()).thenReturn("arn:aws:sts::12345:assumed-role/athenz.zts2/i-13434"); + cloudStore.setGetCallerIdentityResult(mockResult); + AWSInstanceInformation info = new AWSInstanceInformation() + .setDomain("athenz") + .setService("zts") + .setAccount("12345"); + assertFalse(cloudStore.verifyInstanceIdentity(info)); + } + + @Test + public void testVerifyInstanceIdentity() { + MockCloudStore cloudStore = new MockCloudStore(); + GetCallerIdentityResult mockResult = Mockito.mock(GetCallerIdentityResult.class); + Mockito.when(mockResult.getArn()).thenReturn("arn:aws:sts::12345:assumed-role/athenz.zts/i-134343"); + cloudStore.setGetCallerIdentityResult(mockResult); + AWSInstanceInformation info = new AWSInstanceInformation() + .setDomain("athenz") + .setService("zts") + .setAccount("12345"); + assertTrue(cloudStore.verifyInstanceIdentity(info)); +} + + @Test + public void testAssumeAWSRoleAWSNotEnabled() { + CloudStore cloudStore = new CloudStore(null); + try { + cloudStore.assumeAWSRole("account", "sycner", "athenz.syncer"); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), 500); + } + cloudStore.close(); + } + + @Test + public void testAssumeAWSRole() { + MockCloudStore cloudStore = new MockCloudStore(); + cloudStore.awsEnabled = true; + AssumeRoleResult mockResult = Mockito.mock(AssumeRoleResult.class); + Credentials creds = Mockito.mock(Credentials.class); + Mockito.when(creds.getAccessKeyId()).thenReturn("accesskeyid"); + Mockito.when(creds.getSecretAccessKey()).thenReturn("secretaccesskey"); + Mockito.when(creds.getSessionToken()).thenReturn("sessiontoken"); + Mockito.when(creds.getExpiration()).thenReturn(new Date()); + Mockito.when(mockResult.getCredentials()).thenReturn(creds); + cloudStore.setAssumeRoleResult(mockResult); + cloudStore.setAssumeAWSRole(true); + + AWSTemporaryCredentials awsCreds = cloudStore.assumeAWSRole("account", "syncer", "athenz.syncer"); + assertNotNull(awsCreds); + assertEquals(awsCreds.getAccessKeyId(), "accesskeyid"); + assertEquals(awsCreds.getSessionToken(), "sessiontoken"); + assertEquals(awsCreds.getSecretAccessKey(), "secretaccesskey"); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/store/DataStoreTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/DataStoreTest.java new file mode 100644 index 00000000000..a46ab72d63d --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/DataStoreTest.java @@ -0,0 +1,3425 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store; + +import static org.testng.Assert.*; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.utils.SignUtils; +import com.yahoo.athenz.zms.DomainData; +import com.yahoo.athenz.zms.Role; +import com.yahoo.athenz.zms.ServiceIdentity; +import com.yahoo.athenz.zms.SignedDomain; +import com.yahoo.athenz.zms.SignedDomains; +import com.yahoo.athenz.zts.HostServices; +import com.yahoo.athenz.zts.ZTSConsts; +import com.yahoo.athenz.zts.cache.DataCache; +import com.yahoo.athenz.zts.store.DataStore.DataUpdater; +import com.yahoo.athenz.zts.store.file.MockZMSFileChangeLogStore; +import com.yahoo.athenz.zts.store.file.ZMSFileChangeLogStore; +import com.yahoo.rdl.JSON; + +public class DataStoreTest { + + private PrivateKey pkey; + private String userDomain; + + static final String ZTS_DATA_STORE_PATH = "/tmp/zts_server_unit_tests/zts_root"; + static final String ZTS_Y64_CERT0 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RR" + + "RUJBUVVBQTRHTkFEQ0JpUUtCZ1FDMXRHU1ZDQTh3bDVldzVZNzZXajJySkFVRApZYW5FSmZLbUFseDVjUS84a" + + "EtFVWZTU2dwWHIzQ3pkaDFhMjZkbGI3bW1LMjlxbVhKWGg2dW1XOUF5ZlRPS1ZvCis2QVNsb1ZVM2F2dnVmbE" + + "dVT0VnMmpzbWRha1IyNEtjTGpBdTZRclVlNDE3bEczdDhxU1BJR2pTNUMrQ3NKVXcKaDA0aEh4NWYrUEV3eFY" + + "0cmJRSURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + static final String ZTS_PEM_CERT0 = "-----BEGIN PUBLIC KEY-----\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1tGSVCA8wl5ew5Y76Wj2rJAUD\n" + + "YanEJfKmAlx5cQ/8hKEUfSSgpXr3Czdh1a26dlb7mmK29qmXJXh6umW9AyfTOKVo\n" + + "+6ASloVU3avvuflGUOEg2jsmdakR24KcLjAu6QrUe417lG3t8qSPIGjS5C+CsJUw\n" + + "h04hHx5f+PEwxV4rbQIDAQAB\n" + + "-----END PUBLIC KEY-----\n"; + static final String ZTS_Y64_CERT1 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RR" + + "RUJBUVVBQTRHTkFEQ0JpUUtCZ1FETUhWaFRNZldJQWdvTEdhbkx2QkNNRytRdAoySU9pcml2cGRLSFNPSkpsYX" + + "VKRUNlWlY1MTVmWG91SjhRb09IczA4UGlsdXdjeHF5dmhJSlduNWFrVEhGSWh5CkdDNkdtUTUzbG9WSEtTVE1WO" + + "DM1M0FjNkhydzYxbmJZMVQ2TnA2bjdxdXI4a1UwR2tmdk5hWFZrK09LNVBaankKbkxzZ251UjlCeFZndlM4ZjJR" + + "SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + static final String ZTS_PEM_CERT1 = "-----BEGIN PUBLIC KEY-----\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMHVhTMfWIAgoLGanLvBCMG+Qt\n" + + "2IOirivpdKHSOJJlauJECeZV515fXouJ8QoOHs08PiluwcxqyvhIJWn5akTHFIhy\n" + + "GC6GmQ53loVHKSTMV8353Ac6Hrw61nbY1T6Np6n7qur8kU0GkfvNaXVk+OK5PZjy\n" + + "nLsgnuR9BxVgvS8f2QIDAQAB\n" + + "-----END PUBLIC KEY-----\n"; + static final String ZTS_Y64_CERT2 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RR" + + "RUJBUVVBQTRHTkFEQ0JpUUtCZ1FEbmZsZVZ4d293aitRWStjQi8rbWs5YXZYZgpHUWVpTTdOMlMwby9LV3FWK2h" + + "GVWtDZkExMWxEYVJoZUY0alFhSzVaM2pPUE9nbklOZE5hd3VXQ081NUxKdVJRCmI1R0ZSbzhPNjNJNzA3M3ZDZ0V" + + "KdmNST09SdjJDYWhQbnBKbjc3bkhQdlV2Szl0M3JyRURhdi8vanA0UDN5REMKNEVNdHBScmduUXBXNmpJSWlRSUR" + + "BUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + static final String ZTS_PEM_CERT2 = "-----BEGIN PUBLIC KEY-----\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDnfleVxwowj+QY+cB/+mk9avXf\n" + + "GQeiM7N2S0o/KWqV+hFUkCfA11lDaRheF4jQaK5Z3jOPOgnINdNawuWCO55LJuRQ\n" + + "b5GFRo8O63I7073vCgEJvcROORv2CahPnpJn77nHPvUvK9t3rrEDav//jp4P3yDC\n" + + "4EMtpRrgnQpW6jIIiQIDAQAB\n" + + "-----END PUBLIC KEY-----\n"; + static final String ZTS_Y64_CERT3 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RR" + + "RUJBUVVBQTRHTkFEQ0JpUUtCZ1FETWRqSmUwY01wSGR4ZEJKTDcvR2poNTNVUAp5WTdVQ2VlYnZUa2M2S1ZmR0" + + "RnVVlrMUhtaWJ5U21lbnZOYitkNkhXQ1YySGVicUptN1krL2VuaFNkcTR3QTJrCnFtdmFHY09rV1R2cUU2a2J1" + + "MG5LemdUK21jck1sOVpqTHdBQXZPS1hTRi82MTJxQ0tlSElRd3ZtWlB1RkJJTjEKUnFteWgwT0k1aHN5VS9nYj" + + "Z3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + static final String ZTS_PEM_CERT3 = "-----BEGIN PUBLIC KEY-----\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMdjJe0cMpHdxdBJL7/Gjh53UP\n" + + "yY7UCeebvTkc6KVfGDgUYk1HmibySmenvNb+d6HWCV2HebqJm7Y+/enhSdq4wA2k\n" + + "qmvaGcOkWTvqE6kbu0nKzgT+mcrMl9ZjLwAAvOKXSF/612qCKeHIQwvmZPuFBIN1\n" + + "Rqmyh0OI5hsyU/gb6wIDAQAB\n" + + "-----END PUBLIC KEY-----\n"; + private static final String ROLE_POSTFIX = ":role."; + + @BeforeClass + public void setUpClass() throws Exception { + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY_STORE_CLASS, "com.yahoo.athenz.zts.pkey.file.FilePrivateKeyStoreFactory"); + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY, "src/test/resources/zts_private.pem"); + System.setProperty(ZTSConsts.ZTS_PROP_ATHENZ_CONF, "src/test/resources/athenz.conf"); + } + + @BeforeMethod + public void setup() { + + // we want to make sure we start we clean dir structure + + ZMSFileChangeLogStore.deleteDirectory(new File(ZTS_DATA_STORE_PATH)); + + String privKeyName = System.getProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY); + System.out.println("private key file=" + privKeyName); + File privKeyFile = new File(privKeyName); + String privKey = Crypto.encodedFile(privKeyFile); + + pkey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + + userDomain = System.getProperty(ZTSConsts.ZTS_PROP_USER_DOMAIN, "user"); + } + + @AfterMethod + public void cleanup() { + ZMSFileChangeLogStore.deleteDirectory(new File(ZTS_DATA_STORE_PATH)); + } + + private void setServicePublicKey(ServiceIdentity service, String id, String key) { + com.yahoo.athenz.zms.PublicKeyEntry keyEntry = new com.yahoo.athenz.zms.PublicKeyEntry(); + keyEntry.setId(id); + keyEntry.setKey(key); + List listKeys = new ArrayList<>(); + listKeys.add(keyEntry); + service.setPublicKeys(listKeys); + } + + @Test + public void DataStorContstructorTest() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + assertNotNull(store); + } + + @Test + public void testGetDomainListFromZMS() { + + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List list = new ArrayList<>(); + list.add("Test1"); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(list); + + Set zmsDomainList = store.changeLogStore.getServerDomainList(); + assertEquals(zmsDomainList.size(), 1); + assertTrue(zmsDomainList.contains("Test1")); + } + + @Test + public void testGetDomainListFromZMSNullClient() { + + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List list = new ArrayList<>(); + list.add("Test1"); + list.add("Test2"); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(list); + + Set zmsDomainList = store.changeLogStore.getServerDomainList(); + assertEquals(zmsDomainList.size(), 2); + assertTrue(zmsDomainList.contains("Test1")); + assertTrue(zmsDomainList.contains("Test2")); + } + + @Test + public void testGetDomainListFromZMSError() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(null); + + Set zmsDomainList = store.changeLogStore.getServerDomainList(); + assertNull(zmsDomainList); + } + + @Test + public void testLoadZMSPublicKeys() { + + System.setProperty(ZTSConsts.ZTS_PROP_ATHENZ_CONF, "src/test/resources/athenz.conf"); + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + PublicKey zmsKey = store.zmsPublicKeyCache.getIfPresent("0"); + assertNotNull(zmsKey); + assertNull(store.zmsPublicKeyCache.getIfPresent("1")); + } + + @Test + public void testRetrieveLastModificationTime() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + try (PrintWriter out = new PrintWriter("/tmp/zts_server_unit_tests/zts_root/.lastModTime")) { + out.write("{\"lastModTime\":\"12345\"}"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + fail(); + } + + assertEquals(((MockZMSFileChangeLogStore) store.changeLogStore).retrieveLastModificationTime(), "12345"); + } + + @Test + public void testRetrieveLastModificationTimeNotValid() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).retrieveLastModificationTime(); + assertEquals(((MockZMSFileChangeLogStore) store.changeLogStore).lastModTime, null); + } + + @Test + public void testSaveLastModificationTime() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.changeLogStore.setLastModificationTimestamp("23456"); + + String data = null; + File f = new File("/tmp/zts_server_unit_tests/zts_root/.lastModTime"); + try { + data = new String(Files.readAllBytes(f.toPath()), "UTF-8"); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } + + assertEquals(data, "{\"lastModTime\":\"23456\"}"); + } + + @Test + public void testResetLastModificationTime() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.changeLogStore.setLastModificationTimestamp("34567"); + store.changeLogStore.setLastModificationTimestamp(null); + + assertNull(((MockZMSFileChangeLogStore) store.changeLogStore).lastModTime); + File f = new File("/tmp/zts_server_unit_tests/zts_root/.lastModTime"); + assertFalse(f.exists()); + } + + @Test + public void testRemovePublicKeys() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_1", "PublicKey1"); + + Map remKeys = new HashMap<>(); + remKeys.put("sports.storage_0", "PublicKey0"); + + store.removePublicKeys(remKeys); + assertEquals(store.publicKeyCache.size(), 2); + assertTrue(store.publicKeyCache.containsKey("coretech.storage_0")); + assertTrue(store.publicKeyCache.containsKey("sports.storage_1")); + } + + @Test + public void testRemovePublicKeysAll() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_1", "PublicKey1"); + + Map remKeys = new HashMap<>(); + remKeys.put("coretech.storage_0", "PublicKey0"); + remKeys.put("sports.storage_0", "PublicKey0"); + remKeys.put("sports.storage_1", "PublicKey1"); + + store.removePublicKeys(remKeys); + assertEquals(store.publicKeyCache.size(), 0); + } + + @Test + public void testRemovePublicKeysInvalid() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_1", "PublicKey1"); + + Map remKeys = new HashMap<>(); + remKeys.put("sports.storage_2", "PublicKey2"); + + store.removePublicKeys(remKeys); + assertEquals(store.publicKeyCache.size(), 3); + assertTrue(store.publicKeyCache.containsKey("coretech.storage_0")); + assertTrue(store.publicKeyCache.containsKey("sports.storage_0")); + assertTrue(store.publicKeyCache.containsKey("sports.storage_1")); + } + + @Test + public void testRemovePublicKeysEmpty() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + + Map remKeys = new HashMap<>(); + + store.removePublicKeys(remKeys); + assertEquals(store.publicKeyCache.size(), 1); + assertTrue(store.publicKeyCache.containsKey("coretech.storage_0")); + } + + @Test + public void testRemovePublicKeysNull() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + + store.removePublicKeys(null); + assertEquals(store.publicKeyCache.size(), 1); + assertTrue(store.publicKeyCache.containsKey("coretech.storage_0")); + } + + @Test + public void testAddPublicKeys() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_1", "PublicKey1"); + + Map addKeys = new HashMap<>(); + addKeys.put("sports.storage_1", "PublicKey1"); + addKeys.put("sports.storage_2", "PublicKey2"); + + store.addPublicKeys(addKeys); + assertEquals(store.publicKeyCache.size(), 4); + assertTrue(store.publicKeyCache.containsKey("coretech.storage_0")); + assertTrue(store.publicKeyCache.containsKey("sports.storage_0")); + assertTrue(store.publicKeyCache.containsKey("sports.storage_1")); + assertTrue(store.publicKeyCache.containsKey("sports.storage_2")); + } + + @Test + public void testAddPublicKeysUpdateValue() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_1", "PublicKey1"); + + Map addKeys = new HashMap<>(); + addKeys.put("coretech.storage_0", "PublicKey0"); + addKeys.put("sports.storage_0", "PublicKey100"); + addKeys.put("sports.storage_1", "PublicKey101"); + + store.addPublicKeys(addKeys); + assertEquals(store.publicKeyCache.size(), 3); + String value = store.publicKeyCache.get("coretech.storage_0"); + assertEquals(value, "PublicKey0"); + + value = store.publicKeyCache.get("sports.storage_0"); + assertEquals(value, "PublicKey100"); + + value = store.publicKeyCache.get("sports.storage_1"); + assertEquals(value, "PublicKey101"); + } + + @Test + public void testAddPublicKeysEmpty() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + + Map addKeys = new HashMap<>(); + + store.addPublicKeys(addKeys); + assertEquals(store.publicKeyCache.size(), 1); + assertTrue(store.publicKeyCache.containsKey("coretech.storage_0")); + } + + @Test + public void testAddPublicKeysNull() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + + store.addPublicKeys(null); + assertEquals(store.publicKeyCache.size(), 1); + assertTrue(store.publicKeyCache.containsKey("coretech.storage_0")); + } + + @Test + public void testGetPublicKey() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_1", "PublicKey1"); + + Map addKeys = new HashMap<>(); + addKeys.put("sports.storage_1", "PublicKey1"); + addKeys.put("sports.storage_2", "PublicKey2"); + + store.addPublicKeys(addKeys); + assertEquals(store.getPublicKey("coretech", "storage", "0"), "PublicKey0"); + assertEquals(store.getPublicKey("sports", "storage", "0"), "PublicKey0"); + assertEquals(store.getPublicKey("sports", "storage", "1"), "PublicKey1"); + assertEquals(store.getPublicKey("sports", "storage", "2"), "PublicKey2"); + } + + @Test + public void testGetPublicKeyUpdated() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_1", "PublicKey1"); + + Map addKeys = new HashMap<>(); + addKeys.put("coretech.storage_0", "PublicKey0"); + addKeys.put("sports.storage_0", "PublicKey100"); + addKeys.put("sports.storage_1", "PublicKey101"); + + store.addPublicKeys(addKeys); + + assertEquals(store.getPublicKey("coretech", "storage", "0"), "PublicKey0"); + assertEquals(store.getPublicKey("sports", "storage", "0"), "PublicKey100"); + assertEquals(store.getPublicKey("sports", "storage", "1"), "PublicKey101"); + } + + @Test + public void testGetPublicKeyInvalid() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.publicKeyCache.put("coretech.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_0", "PublicKey0"); + store.publicKeyCache.put("sports.storage_1", "PublicKey1"); + + Map addKeys = new HashMap<>(); + addKeys.put("sports.storage_1", "PublicKey1"); + addKeys.put("sports.storage_2", "PublicKey2"); + + store.addPublicKeys(addKeys); + assertNull(store.getPublicKey("weather", "storage", "0")); + assertNull(store.getPublicKey("sports", "storage", "101")); + assertNull(store.getPublicKey("sports", "backup", "0")); + assertNull(store.getPublicKey("sports", "storage", "-1")); + } + + @Test + public void testAddHostEntriesNotPresent() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + Map> hostMap = new HashMap<>(); + Set services = new HashSet<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + hostMap.put("host1", services); + + services = new HashSet<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + hostMap.put("host2", services); + + store.addHostEntries(hostMap); + assertEquals(store.hostCache.size(), 2); + List retServices = store.hostCache.get("host1"); + assertEquals(retServices.size(), 2); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + + retServices = store.hostCache.get("host2"); + assertEquals(retServices.size(), 2); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + } + + @Test + public void testAddHostEntriesAddValue() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + List services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host1", services); + + services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host2", services); + + Map> hostMap = new HashMap<>(); + Set newServices = new HashSet<>(); + newServices.add("sports.storage"); + hostMap.put("host2", newServices); + hostMap.put("host3", newServices); + + store.addHostEntries(hostMap); + assertEquals(store.hostCache.size(), 3); + List retServices = store.hostCache.get("host1"); + assertEquals(retServices.size(), 2); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + + retServices = store.hostCache.get("host2"); + assertEquals(retServices.size(), 3); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + assertTrue(retServices.contains("sports.storage")); + + retServices = store.hostCache.get("host3"); + assertEquals(retServices.size(), 1); + assertTrue(retServices.contains("sports.storage")); + } + + @Test + public void testAddHostEntriesEmptyMap() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + List services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host1", services); + + Map> hostMap = new HashMap<>(); + + store.addHostEntries(hostMap); + assertEquals(store.hostCache.size(), 1); + List retServices = store.hostCache.get("host1"); + assertEquals(retServices.size(), 2); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + } + + @Test + public void testAddHostEntrieNullMap() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + List services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host1", services); + + services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host2", services); + + store.addHostEntries(null); + assertEquals(store.hostCache.size(), 2); + List retServices = store.hostCache.get("host1"); + assertEquals(retServices.size(), 2); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + + retServices = store.hostCache.get("host2"); + assertEquals(retServices.size(), 2); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + } + + @Test + public void testRemoveHostEntriesNotPresent() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + List services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host1", services); + + services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host2", services); + + Map> hostMap = new HashMap<>(); + Set newServices = new HashSet<>(); + newServices.add("coretech.storage"); + newServices.add("coretech.backup"); + hostMap.put("host3", newServices); + + store.removeHostEntries(hostMap); + assertEquals(store.hostCache.size(), 2); + List retServices = store.hostCache.get("host1"); + assertEquals(retServices.size(), 2); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + + retServices = store.hostCache.get("host2"); + assertEquals(retServices.size(), 2); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + } + + @Test + public void testRemoveHostEntriesRemoveValue() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + List services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host1", services); + + services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host2", services); + + Map> hostMap = new HashMap<>(); + Set newServices = new HashSet<>(); + newServices.add("coretech.storage"); + hostMap.put("host1", newServices); + + newServices = new HashSet<>(); + newServices.add("coretech.backup"); + hostMap.put("host2", newServices); + + store.removeHostEntries(hostMap); + assertEquals(store.hostCache.size(), 2); + List retServices = store.hostCache.get("host1"); + assertEquals(retServices.size(), 1); + assertTrue(retServices.contains("coretech.backup")); + + retServices = store.hostCache.get("host2"); + assertEquals(retServices.size(), 1); + assertTrue(retServices.contains("coretech.storage")); + } + + @Test + public void testRemoveHostEntriesEmptyMap() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + List services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host1", services); + + Map> hostMap = new HashMap<>(); + + store.removeHostEntries(hostMap); + assertEquals(store.hostCache.size(), 1); + List retServices = store.hostCache.get("host1"); + assertEquals(retServices.size(), 2); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + } + + @Test + public void testRemoveHostEntrieNullMap() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + List services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host1", services); + + services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("coretech.backup"); + store.hostCache.put("host2", services); + + store.removeHostEntries(null); + assertEquals(store.hostCache.size(), 2); + List retServices = store.hostCache.get("host1"); + assertEquals(retServices.size(), 2); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + + retServices = store.hostCache.get("host2"); + assertEquals(retServices.size(), 2); + assertTrue(retServices.contains("coretech.storage")); + assertTrue(retServices.contains("coretech.backup")); + } + + @Test + public void testGetHostServices() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + List services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("sports.storage"); + store.hostCache.put("host1", services); + + services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("sports.storage"); + store.hostCache.put("host2", services); + + HostServices hostServices = store.getHostServices("host1"); + List hosts = hostServices.getNames(); + assertEquals(hosts.size(), 2); + assertTrue(hosts.contains("coretech.storage")); + assertTrue(hosts.contains("sports.storage")); + + hostServices = store.getHostServices("host2"); + hosts = hostServices.getNames(); + assertEquals(hosts.size(), 2); + assertTrue(hosts.contains("coretech.storage")); + assertTrue(hosts.contains("sports.storage")); + } + + @Test + public void testGetHostServicesHostUpdated() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + List services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("sports.storage"); + store.hostCache.put("host1", services); + + services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("sports.storage"); + store.hostCache.put("host2", services); + + Map> hostMap = new HashMap<>(); + Set newServices = new HashSet<>(); + newServices.add("coretech.backup"); + hostMap.put("host3", newServices); + + newServices = new HashSet<>(); + newServices.add("sports.backup"); + hostMap.put("host1", newServices); + + store.addHostEntries(hostMap); + + Map> remMap = new HashMap<>(); + Set remServices = new HashSet<>(); + remServices.add("sports.storage"); + remMap.put("host1", remServices); + + store.removeHostEntries(remMap); + + HostServices hostServices = store.getHostServices("host1"); + List hosts = hostServices.getNames(); + assertEquals(hosts.size(), 2); + assertTrue(hosts.contains("coretech.storage")); + assertTrue(hosts.contains("sports.backup")); + + hostServices = store.getHostServices("host2"); + hosts = hostServices.getNames(); + assertEquals(hosts.size(), 2); + assertTrue(hosts.contains("coretech.storage")); + assertTrue(hosts.contains("sports.storage")); + + hostServices = store.getHostServices("host3"); + hosts = hostServices.getNames(); + assertEquals(hosts.size(), 1); + assertTrue(hosts.contains("coretech.backup")); + } + + @Test + public void testGetHostServicesInvalid() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + List services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("sports.storage"); + store.hostCache.put("host1", services); + + services = new ArrayList<>(); + services.add("coretech.storage"); + services.add("sports.storage"); + store.hostCache.put("host2", services); + + HostServices hostServices = store.getHostServices("host3"); + List hosts = hostServices.getNames(); + assertNull(hosts); + } + + @Test + public void testGenerateServiceKeyName() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + assertEquals(store.generateServiceKeyName("coretech", "storage", "3"), "coretech.storage_3"); + } + + @Test + public void testGenerateServiceKeyNameLongValues() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + String domain = "coretech0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; + String service = "coretech0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; + String expectedValue = domain + "." + service + "_2"; + assertEquals(store.generateServiceKeyName(domain, service, "2"), expectedValue); + } + + @Test + public void testCheckRoleSet() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + Set checkSet = new HashSet<>(); + checkSet.add("role1"); + checkSet.add("role2"); + + assertTrue(store.checkRoleSet("test1", null)); + assertTrue(store.checkRoleSet("role1", checkSet)); + assertTrue(store.checkRoleSet("role2", checkSet)); + assertFalse(store.checkRoleSet("role3", checkSet)); + } + + @Test + public void testAddRoleToListPrefixNoMatch() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + List accessibleRoles = new ArrayList<>(); + store.addRoleToList("sports:role.admin", "coretech:role.", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testAddRoleToListNullSuffix() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + store.addRoleToList("coretech:role.admin", "coretech:role.", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 1); + assertEquals(accessibleRoles.get(0), "admin"); + } + + @Test + public void testAddRoleToList() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + store.addRoleToList("coretech:role.admin", "coretech:role.", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 1); + assertEquals(accessibleRoles.get(0), "admin"); + } + + @Test + public void testAddDomainToCacheNewDomain() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + + List roles = new ArrayList<>(); + roles.add(role); + + DomainData domainData = new DomainData(); + domainData.setRoles(roles); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + DomainData domain = store.getDomainData("coretech"); + assertNotNull(domain); + assertEquals(domain.getRoles().size(), 1); + assertEquals(domain.getRoles().get(0).getName(), "coretech:role.admin"); + assertEquals(domain.getRoles().get(0).getMembers().size(), 1); + assertTrue(domain.getRoles().get(0).getMembers().contains("user_domain.user")); + } + + @Test + public void testAddDomainToCacheUpdatedDomain() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + + List roles = new ArrayList<>(); + roles.add(role); + + DomainData domainData = new DomainData(); + domainData.setRoles(roles); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + /* update member list */ + + role = new Role(); + role.setName("coretech:role.admin"); + members = new ArrayList<>(); + members.add("user_domain.user1"); + members.add("user_domain.user2"); + role.setMembers(members); + + roles = new ArrayList<>(); + roles.add(role); + + dataCache = new DataCache(); + domainData = new DomainData(); + domainData.setRoles(roles); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + DomainData domain = store.getDomainData("coretech"); + assertNotNull(domain); + assertEquals(domain.getRoles().size(), 1); + assertEquals(domain.getRoles().get(0).getName(), "coretech:role.admin"); + assertEquals(domain.getRoles().get(0).getMembers().size(), 2); + assertTrue(domain.getRoles().get(0).getMembers().contains("user_domain.user1")); + assertTrue(domain.getRoles().get(0).getMembers().contains("user_domain.user2")); + } + + @Test + public void testAddDomainToCacheSameHosts() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("coretech.storage"); + + List hosts = new ArrayList<>(); + hosts.add("host1"); + service.setHosts(hosts); + + List services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + DomainData domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + /* same hosts - no changes */ + + dataCache = new DataCache(); + service = new ServiceIdentity(); + service.setName("coretech.storage"); + + hosts = new ArrayList<>(); + hosts.add("host1"); + service.setHosts(hosts); + + services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + HostServices hostServices = store.getHostServices("host1"); + hosts = hostServices.getNames(); + assertEquals(hosts.size(), 1); + assertTrue(hosts.contains("coretech.storage")); + } + + @Test + public void testAddDomainToCacheAddedHosts() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("coretech.storage"); + + List hosts = new ArrayList<>(); + hosts.add("host1"); + service.setHosts(hosts); + + List services = new ArrayList<>(); + dataCache.processServiceIdentity(service); + services.add(service); + + DomainData domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + /* added hosts */ + + dataCache = new DataCache(); + service = new ServiceIdentity(); + service.setName("coretech.storage"); + + hosts = new ArrayList<>(); + hosts.add("host1"); + hosts.add("host2"); + service.setHosts(hosts); + + services = new ArrayList<>(); + dataCache.processServiceIdentity(service); + services.add(service); + + domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + HostServices hostServices = store.getHostServices("host1"); + hosts = hostServices.getNames(); + assertEquals(hosts.size(), 1); + assertTrue(hosts.contains("coretech.storage")); + + hostServices = store.getHostServices("host2"); + hosts = hostServices.getNames(); + assertEquals(hosts.size(), 1); + assertTrue(hosts.contains("coretech.storage")); + } + + @Test + public void testAddDomainToCacheRemovedHosts() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("coretech.storage"); + + List hosts = new ArrayList<>(); + hosts.add("host1"); + hosts.add("host2"); + hosts.add("host3"); + service.setHosts(hosts); + + List services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + DomainData domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + /* removed hosts */ + + dataCache = new DataCache(); + service = new ServiceIdentity(); + service.setName("coretech.storage"); + + hosts = new ArrayList<>(); + hosts.add("host1"); + service.setHosts(hosts); + + services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + HostServices hostServices = store.getHostServices("host1"); + hosts = hostServices.getNames(); + assertEquals(hosts.size(), 1); + assertTrue(hosts.contains("coretech.storage")); + + hostServices = store.getHostServices("host2"); + hosts = hostServices.getNames(); + assertEquals(hosts.size(), 0); + + hostServices = store.getHostServices("host3"); + hosts = hostServices.getNames(); + assertEquals(hosts.size(), 0); + } + + @Test + public void testAddDomainToCacheSamePublicKeys() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("coretech.storage"); + + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + + com.yahoo.athenz.zms.PublicKeyEntry publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT1); + publicKey.setId("1"); + + List publicKeys = new ArrayList(); + publicKeys.add(publicKey); + + service.setPublicKeys(publicKeys); + + List services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + DomainData domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + /* same public keys - no changes */ + + dataCache = new DataCache(); + service = new ServiceIdentity(); + service.setName("coretech.storage"); + + publicKeys = new ArrayList(); + + publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT0); + publicKey.setId("0"); + publicKeys.add(publicKey); + + publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT1); + publicKey.setId("1"); + publicKeys.add(publicKey); + + service.setPublicKeys(publicKeys); + + services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + assertEquals(store.getPublicKey("coretech", "storage", "0"), ZTS_PEM_CERT0); + assertEquals(store.getPublicKey("coretech", "storage", "1"), ZTS_PEM_CERT1); + assertNull(store.getPublicKey("coretech", "storage", "2")); + } + + @Test + public void testAddDomainToCacheUpdatedPublicKeysV0() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("coretech.storage"); + + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + + com.yahoo.athenz.zms.PublicKeyEntry publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT1); + publicKey.setId("1"); + + List publicKeys = new ArrayList(); + publicKeys.add(publicKey); + + service.setPublicKeys(publicKeys); + + List services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + DomainData domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + /* update V0 public key */ + + dataCache = new DataCache(); + service = new ServiceIdentity(); + service.setName("coretech.storage"); + + publicKeys = new ArrayList(); + + publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT2); + publicKey.setId("0"); + publicKeys.add(publicKey); + + publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT1); + publicKey.setId("1"); + publicKeys.add(publicKey); + + service.setPublicKeys(publicKeys); + + services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + assertEquals(store.getPublicKey("coretech", "storage", "0"), ZTS_PEM_CERT2); + assertEquals(store.getPublicKey("coretech", "storage", "1"), ZTS_PEM_CERT1); + assertNull(store.getPublicKey("coretech", "storage", "2")); + } + + @Test + public void testAddDomainToCacheUpdatedPublicKeysVersions() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("coretech.storage"); + + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + + com.yahoo.athenz.zms.PublicKeyEntry publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT1); + publicKey.setId("1"); + + List publicKeys = new ArrayList(); + publicKeys.add(publicKey); + + service.setPublicKeys(publicKeys); + + List services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + DomainData domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + /* update multiple version public keys */ + + dataCache = new DataCache(); + service = new ServiceIdentity(); + service.setName("coretech.storage"); + + publicKeys = new ArrayList(); + + publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT0); + publicKey.setId("0"); + publicKeys.add(publicKey); + + publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT3); + publicKey.setId("1"); + publicKeys.add(publicKey); + + publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT2); + publicKey.setId("2"); + publicKeys.add(publicKey); + + service.setPublicKeys(publicKeys); + + services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + assertEquals(store.getPublicKey("coretech", "storage", "0"), ZTS_PEM_CERT0); + assertEquals(store.getPublicKey("coretech", "storage", "1"), ZTS_PEM_CERT3); + assertEquals(store.getPublicKey("coretech", "storage", "2"), ZTS_PEM_CERT2); + assertNull(store.getPublicKey("coretech", "storage", "3")); + } + + @Test + public void testAddDomainToCacheRemovedPublicKeysV0() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("coretech.storage"); + + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + + com.yahoo.athenz.zms.PublicKeyEntry publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT1); + publicKey.setId("1"); + + List publicKeys = new ArrayList(); + publicKeys.add(publicKey); + + service.setPublicKeys(publicKeys); + + List services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + DomainData domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + /* remove V0 public key */ + + dataCache = new DataCache(); + service = new ServiceIdentity(); + service.setName("coretech.storage"); + + publicKeys = new ArrayList(); + + publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT1); + publicKey.setId("1"); + publicKeys.add(publicKey); + + service.setPublicKeys(publicKeys); + + services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + assertNull(store.getPublicKey("coretech", "storage", "0")); + assertEquals(store.getPublicKey("coretech", "storage", "1"), ZTS_PEM_CERT1); + assertNull(store.getPublicKey("coretech", "storage", "2")); + } + + @Test + public void testAddDomainToCacheRemovedPublicKeysVersions() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("coretech.storage"); + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + + List publicKeys = new ArrayList(); + + com.yahoo.athenz.zms.PublicKeyEntry publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT1); + publicKey.setId("1"); + publicKeys.add(publicKey); + + publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT2); + publicKey.setId("2"); + publicKeys.add(publicKey); + + service.setPublicKeys(publicKeys); + + List services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + DomainData domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + /* update multiple version public keys */ + + dataCache = new DataCache(); + service = new ServiceIdentity(); + service.setName("coretech.storage"); + + publicKeys = new ArrayList(); + + publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT0); + publicKey.setId("0"); + publicKeys.add(publicKey); + + publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT2); + publicKey.setId("2"); + publicKeys.add(publicKey); + + service.setPublicKeys(publicKeys); + + services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + assertEquals(store.getPublicKey("coretech", "storage", "0"), ZTS_PEM_CERT0); + assertNull(store.getPublicKey("coretech", "storage", "1")); + assertEquals(store.getPublicKey("coretech", "storage", "2"), ZTS_PEM_CERT2); + assertNull(store.getPublicKey("coretech", "storage", "3")); + } + + @Test + public void testDeleteDomain() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + + DomainData domainData = new DomainData(); + domainData.setName("coretech"); + domainData.setRoles(roles); + + signedDomain.setDomain(domainData); + signedDomain.setKeyId("0"); + ((MockZMSFileChangeLogStore) store.changeLogStore).put("coretech", JSON.bytes(signedDomain)); + + DataCache dataCache = new DataCache(); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + store.deleteDomainFromCache("coretech"); + store.changeLogStore.removeLocalDomain("coretech"); + + assertNull(store.getCacheStore().getIfPresent("coretech")); + + File file = new File("/tmp/zts_server_unit_tests/zts_root/coretech"); + assertFalse(file.exists()); + } + + @Test + public void testDeleteDomainFromCacheHosts() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("coretech.storage"); + + List hosts = new ArrayList<>(); + hosts.add("host1"); + service.setHosts(hosts); + + List services = new ArrayList<>(); + dataCache.processServiceIdentity(service); + services.add(service); + + DomainData domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + store.deleteDomainFromCache("coretech"); + + HostServices hostServices = store.getHostServices("host1"); + hosts = hostServices.getNames(); + assertEquals(hosts.size(), 0); + } + + @Test + public void testDeleteDomainFromCachePublicKeys() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("coretech.storage"); + + setServicePublicKey(service, "0", ZTS_Y64_CERT0); + + com.yahoo.athenz.zms.PublicKeyEntry publicKey = new com.yahoo.athenz.zms.PublicKeyEntry(); + publicKey.setKey(ZTS_Y64_CERT1); + publicKey.setId("1"); + + List publicKeys = new ArrayList(); + publicKeys.add(publicKey); + + service.setPublicKeys(publicKeys); + + List services = new ArrayList<>(); + services.add(service); + dataCache.processServiceIdentity(service); + + DomainData domainData = new DomainData(); + domainData.setServices(services); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + store.deleteDomainFromCache("coretech"); + + assertNull(store.getPublicKey("coretech", "storage", "0")); + assertNull(store.getPublicKey("coretech", "storage", "1")); + assertNull(store.getPublicKey("coretech", "storage", "2")); + } + + @Test + public void testDeleteDomainFromCacheServices() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + + DomainData domainData = new DomainData(); + domainData.setName("coretech"); + domainData.setRoles(roles); + + signedDomain.setDomain(domainData); + signedDomain.setKeyId("0"); + ((MockZMSFileChangeLogStore) store.changeLogStore).put("coretech", JSON.bytes(signedDomain)); + + DataCache dataCache = new DataCache(); + dataCache.setDomainData(domainData); + + store.addDomainToCache("coretech", dataCache); + + store.deleteDomainFromCache("coretech"); + store.changeLogStore.removeLocalDomain("coretech"); + + assertNull(store.getCacheStore().getIfPresent("coretech")); + + File file = new File("/tmp/zts_server_unit_tests/zts_root/coretech"); + assertFalse(file.exists()); + } + + @Test + public void testRetrieveTagHeadersEmptyList() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setTagHeader(null); + + Map> responseHeaders = new HashMap>(); + assertNull(((MockZMSFileChangeLogStore) store.changeLogStore).retrieveTagHeader(responseHeaders)); + } + + @Test + public void testRetrieveTagHeadersValueNotFound() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setTagHeader(null); + + Map> responseHeaders = new HashMap>(); + List values = new ArrayList<>(); + values.add("Unit-Test"); + responseHeaders.put("User-Agent", values); + assertNull(((MockZMSFileChangeLogStore) store.changeLogStore).retrieveTagHeader(responseHeaders)); + } + + @Test + public void testRetrieveTagHeadersValidValue() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setTagHeader(null); + + Map> responseHeaders = new HashMap>(); + List values = new ArrayList<>(); + values.add("Unit-Test"); + responseHeaders.put("tag", values); + assertEquals(((MockZMSFileChangeLogStore) store.changeLogStore).retrieveTagHeader(responseHeaders), "Unit-Test"); + } + + @Test + public void testValidateSignedDomainValid() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + assertTrue(store.validateSignedDomain(signedDomain)); + } + + @Test + public void testValidateSignedDomainInvalidSignature() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + + DomainData domain = new DomainData(); + domain.setRoles(roles); + + signedDomain.setDomain(domain); + signedDomain.setSignature("InvalidSignature"); + signedDomain.setKeyId("0"); + + assertFalse(store.validateSignedDomain(signedDomain)); + } + + @Test + public void testValidateSignedDomainInvalidVersion() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + + DomainData domain = new DomainData(); + domain.setRoles(roles); + + signedDomain.setDomain(domain); + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), pkey)); + signedDomain.setKeyId("100"); + + assertFalse(store.validateSignedDomain(signedDomain)); + } + + @Test + public void testValidateSignedDomainMissingRole() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + roles.add(role); + + DomainData domain = new DomainData(); + domain.setRoles(roles); + + signedDomain.setDomain(domain); + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), pkey)); + signedDomain.setKeyId("0"); + + domain.setRoles(null); + signedDomain.setDomain(domain); + + assertFalse(store.validateSignedDomain(signedDomain)); + } + + @Test + public void testProcessDomainDeletesNoLocalDomains() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + assertTrue(store.processDomainDeletes()); + } + + private void addDomainToDataStore(DataStore store, String domainName) { + + SignedDomain signedDomain = new SignedDomain(); + + Role role = new Role(); + role.setName(domainName + ":role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + + List roles = new ArrayList<>(); + roles.add(role); + + DomainData domainData = new DomainData(); + domainData.setName(domainName); + domainData.setRoles(roles); + + signedDomain.setDomain(domainData); + signedDomain.setKeyId("0"); + ((MockZMSFileChangeLogStore) store.changeLogStore).put(domainName, JSON.bytes(signedDomain)); + + DataCache dataCache = new DataCache(); + dataCache.setDomainData(domainData); + + store.addDomainToCache(domainName, dataCache); + } + + @Test + public void testProcessDomainDeletesZMSFailure() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(null); + addDomainToDataStore(store, "coretech"); + + /* this should throw an exception when obtaining domain list from ZMS */ + + assertFalse(store.processDomainDeletes()); + } + + @Test + public void testProcessDomainDeletesZMSSingleDelete() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + addDomainToDataStore(store, "coretech"); + addDomainToDataStore(store, "sports"); + + List list = new ArrayList<>(); + list.add(userDomain); + list.add("sys.auth"); + list.add("coretech"); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(list); + + assertTrue(store.processDomainDeletes()); + assertNull(store.getDomainData("sports")); + assertNotNull(store.getDomainData("coretech")); + } + + @Test + public void testProcessDomainDeletesZMSAllDelete() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + addDomainToDataStore(store, "coretech"); + addDomainToDataStore(store, "sports"); + + List list = new ArrayList<>(); + list.add(userDomain); + list.add("sys.auth"); + list.add("coretech2"); + list.add("sports2"); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(list); + + assertTrue(store.processDomainDeletes()); + assertNull(store.getDomainData("sports")); + assertNull(store.getDomainData("coretech")); + } + + @Test + public void testGetAccessibleRoles() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + List accessibleRoles = new ArrayList<>(); + DataCache data = store.getDataCache("coretech"); + store.getAccessibleRoles(data, "coretech", "user_domain.user1", null, accessibleRoles, false); + + assertEquals(accessibleRoles.size(), 1); + assertTrue(accessibleRoles.contains("writers")); + } + + @Test + public void testGetAccessibleRolesInvalidDomain() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + List accessibleRoles = new ArrayList<>(); + DataCache data = store.getDataCache("sports"); + store.getAccessibleRoles(data, "sports", "user_domain.user", null, accessibleRoles, false); + + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testGetAccessibleRolesSpecifiedRole() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + List accessibleRoles = new ArrayList<>(); + DataCache data = store.getDataCache("coretech"); + store.getAccessibleRoles(data, "coretech", "user_domain.user", "coretech:role.admin", accessibleRoles, false); + + assertEquals(accessibleRoles.size(), 1); + assertTrue(accessibleRoles.contains("admin")); + } + + @Test + public void testGetAccessibleRolesNoRoles() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + List accessibleRoles = new ArrayList<>(); + DataCache data = store.getDataCache("coretech"); + store.getAccessibleRoles(data, "coretech", "user_domain.nonexistentuser", null, accessibleRoles, false); + + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testGetAccessibleRolesMultipleRoles() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + List accessibleRoles = new ArrayList<>(); + DataCache data = store.getDataCache("coretech"); + store.getAccessibleRoles(data, "coretech", "user_domain.user", null, accessibleRoles, false); + + assertEquals(accessibleRoles.size(), 2); + assertTrue(accessibleRoles.contains("admin")); + assertTrue(accessibleRoles.contains("writers")); + } + + @Test + public void testStoreInitNoLastModTimeLocalDomainDelete() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.changeLogStore.setLastModificationTimestamp(null); + + /* this domain will be deleted since our last refresh is 0 */ + + addDomainToDataStore(store, "coretech"); + File file = new File("/tmp/zts_server_unit_tests/zts_root/coretech"); + assertTrue(file.exists()); + + /* initialize our datastore which will call init */ + + clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + store = new DataStore(clogStore, null); + assertNull(store.getDomainData("coretech")); + assertFalse(file.exists()); + } + + @Test + public void testStoreInitNoLocalDomains() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setTagHeader("2014-01-01T12:00:00"); + ((MockZMSFileChangeLogStore) store.changeLogStore).setLastModificationTimestamp(null); + + List domains = new ArrayList<>(); + + /* we're going to create a new domain */ + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + domains.add(signedDomain); + + /* we're going to update the coretech domain and set new roles */ + + signedDomain = createSignedDomain("sports", "weather"); + domains.add(signedDomain); + + SignedDomains signedDomains = new SignedDomains(); + signedDomains.setDomains(domains); + + ((MockZMSFileChangeLogStore) store.changeLogStore).setSignedDomains(signedDomains); + + boolean result = store.init(); + assertTrue(result); + + List accessibleRoles = new ArrayList<>(); + DataCache data = store.getDataCache("coretech"); + store.getAccessibleRoles(data, "coretech", "user_domain.user", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 2); + assertTrue(accessibleRoles.contains("admin")); + assertTrue(accessibleRoles.contains("writers")); + + accessibleRoles = new ArrayList<>(); + data = store.getDataCache("sports"); + store.getAccessibleRoles(data, "sports", "user_domain.user", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 2); + assertTrue(accessibleRoles.contains("admin")); + assertTrue(accessibleRoles.contains("writers")); + } + + @Test + public void testStoreInitNoLastModTimeDomainUpdateFailure() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + addDomainToDataStore(store, "coretech"); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setTagHeader(null); + + /* our mock is going to throw an exception for domain list so failure */ + + assertFalse(store.init()); + } + + @Test + public void testStoreInitLocalDomainUpdated() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore setupStore = new DataStore(clogStore, null); + setupStore.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + setupStore.processDomain(signedDomain, true); + + /* create a new store instance */ + + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setTagHeader("2014-01-01T12:00:00"); + + List domains = new ArrayList<>(); + + /* we're going to create a new domain */ + + signedDomain = createSignedDomain("sports", "weather"); + domains.add(signedDomain); + + /* we're going to update the coretech domain and set new roles */ + + signedDomain = createSignedDomain("coretech", "weather"); + + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user8"); + role.setMembers(members); + + List roles = new ArrayList<>(); + roles.add(role); + signedDomain.getDomain().setRoles(roles); + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(signedDomain.getDomain()), pkey)); + domains.add(signedDomain); + + SignedDomains signedDomains = new SignedDomains(); + signedDomains.setDomains(domains); + + ((MockZMSFileChangeLogStore) store.changeLogStore).setSignedDomains(signedDomains); + + List domainNames = new ArrayList<>(); + domainNames.add("coretech"); + domainNames.add("sports"); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(domainNames); + + boolean result = store.init(); + assertTrue(result); + + List accessibleRoles = new ArrayList<>(); + DataCache data = store.getDataCache("coretech"); + store.getAccessibleRoles(data, "coretech", "user_domain.user1", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + + accessibleRoles = new ArrayList<>(); + store.getAccessibleRoles(data, "coretech", "user_domain.user8", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 1); + assertTrue(accessibleRoles.contains("admin")); + + accessibleRoles = new ArrayList<>(); + data = store.getDataCache("sports"); + store.getAccessibleRoles(data, "sports", "user_domain.user", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 2); + assertTrue(accessibleRoles.contains("admin")); + assertTrue(accessibleRoles.contains("writers")); + } + + @Test + public void testStoreInitLastModTimeDomainCorrupted() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setLastModificationTimestamp("2014-01-01T12:00:00"); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + signedDomain.setSignature("ABCD"); /* invalid signature which will cause domain to be deleted */ + + ((MockZMSFileChangeLogStore) store.changeLogStore).put("coretech", JSON.bytes(signedDomain)); + + store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setTagHeader("2014-01-01T12:00:00"); + + List list = new ArrayList<>(); + list.add("coretech"); + list.add("sports"); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(list); + + List domains = new ArrayList<>(); + + /* we're going to create a new domain */ + + signedDomain = createSignedDomain("sports", "weather"); + domains.add(signedDomain); + + SignedDomains signedDomains = new SignedDomains(); + signedDomains.setDomains(domains); + + ((MockZMSFileChangeLogStore) store.changeLogStore).setSignedDomains(signedDomains); + + boolean result = store.init(); + assertTrue(result); + + List accessibleRoles = new ArrayList<>(); + DataCache data = store.getDataCache("coretech"); + store.getAccessibleRoles(data, "coretech", "user_domain.user", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + + File file = new File("/tmp/zts_server_unit_tests/zts_root/coretech"); + assertFalse(file.exists()); + + accessibleRoles = new ArrayList<>(); + data = store.getDataCache("sports"); + store.getAccessibleRoles(data, "sports", "user_domain.user", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 2); + assertTrue(accessibleRoles.contains("admin")); + assertTrue(accessibleRoles.contains("writers")); + } + + @Test + public void testProcessDomainRoles() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName("coretech:role.readers"); + members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + roles.add(role); + + DomainData domainData = new DomainData(); + domainData.setName("coretech"); + domainData.setRoles(roles); + + DataCache dataCache = new DataCache(); + dataCache.setDomainData(domainData); + + store.processDomainRoles(domainData, dataCache); + assertEquals(dataCache.getMemberRoleSet("user_domain.user").size(), 2); + + assertTrue(dataCache.getMemberRoleSet("user_domain.user").contains("coretech:role.admin")); + assertTrue(dataCache.getMemberRoleSet("user_domain.user").contains("coretech:role.readers")); + } + + @Test + public void testProcessDomainRolesNullRoles() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DomainData domainData = new DomainData(); + domainData.setName("coretech"); + + DataCache dataCache = new DataCache(); + dataCache.setDomainData(domainData); + + store.processDomainRoles(domainData, dataCache); + assertEquals(dataCache.getMemberCount(), 0); + } + + @Test + public void testProcessDomainPolicies() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List policies = new ArrayList<>(); + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource("sports:role.readers"); + assertion.setAction("assume_role"); + assertion.setRole("coretech:role.readers"); + + List assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policies.add(policy); + + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName("coretech:role.readers"); + members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + roles.add(role); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain("coretech"); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), pkey)); + signedPolicies.setKeyId("0"); + + DomainData domainData = new DomainData(); + domainData.setName("coretech"); + domainData.setPolicies(signedPolicies); + domainData.setRoles(roles); + + DataCache dataCache = new DataCache(); + dataCache.setDomainData(domainData); + + store.processDomainRoles(domainData, dataCache); + assertEquals(dataCache.getMemberRoleSet("user_domain.user").size(), 2); + assertTrue(dataCache.getMemberRoleSet("user_domain.user").contains("coretech:role.admin")); + assertTrue(dataCache.getMemberRoleSet("user_domain.user").contains("coretech:role.readers")); + } + + @Test + public void testProcessDomainPoliciesNullPolicies() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DomainData domainData = new DomainData(); + domainData.setName("coretech"); + + DataCache dataCache = new DataCache(); + dataCache.setDomainData(domainData); + + store.processDomainPolicies(domainData, dataCache); + assertEquals(dataCache.getMemberCount(), 0); + } + + @Test + public void testProcessDomainServiceIdentities() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + ServiceIdentity service = new ServiceIdentity(); + service.setName("coretech.storage"); + + List hosts = new ArrayList<>(); + hosts.add("host1"); + service.setHosts(hosts); + + List services = new ArrayList<>(); + services.add(service); + + DomainData domainData = new DomainData(); + domainData.setName("coretech"); + domainData.setServices(services); + + DataCache dataCache = new DataCache(); + dataCache.setDomainData(domainData); + + store.processDomainServiceIdentities(domainData, dataCache); + store.addDomainToCache(domainData.getName(), dataCache); + + HostServices hostServices = store.getHostServices("host1"); + hosts = hostServices.getNames(); + assertEquals(hosts.size(), 1); + assertTrue(hosts.contains("coretech.storage")); + } + + @Test + public void testProcessDomainServiceIdentitiesNullPolicies() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + DataCache dataCache = new DataCache(); + DomainData domainData = new DomainData(); + domainData.setName("coretech"); + dataCache.setDomainData(domainData); + + store.processDomainServiceIdentities(domainData, dataCache); + + HostServices hostServices = store.getHostServices("host1"); + List hosts = hostServices.getNames(); + assertNull(hosts); + } + + private SignedDomain createSignedDomain(String domainName, String tenantDomain) { + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName(domainName + ":role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName(domainName + ":role.writers"); + members = new ArrayList<>(); + members.add("user_domain.user"); + members.add("user_domain.user1"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName(domainName + ":role.readers"); + members = new ArrayList<>(); + members.add("user_domain.user3"); + members.add("user_domain.user4"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName(domainName + ":role.tenant.readers"); + role.setTrust(tenantDomain); + roles.add(role); + + List policies = new ArrayList<>(); + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + ":tenant.weather.*"); + assertion.setAction("read"); + assertion.setRole(domainName + ":role.tenant.readers"); + + List assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(domainName + ":policy.tenant.reader"); + policies.add(policy); + + ServiceIdentity service = new ServiceIdentity(); + service.setName(domainName + ".storage"); + setServicePublicKey(service, "0", "abcdefgh"); + + List hosts = new ArrayList<>(); + hosts.add("host1"); + service.setHosts(hosts); + + List services = new ArrayList<>(); + services.add(service); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain(domainName); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), pkey)); + signedPolicies.setKeyId("0"); + + DomainData domain = new DomainData(); + domain.setName(domainName); + domain.setRoles(roles); + domain.setServices(services); + domain.setPolicies(signedPolicies); + + signedDomain.setDomain(domain); + + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), pkey)); + signedDomain.setKeyId("0"); + + return signedDomain; + } + + private SignedDomain createTenantSignedDomain(String domainName, String providerDomain) { + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName(domainName + ":role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user"); + role.setMembers(members); + roles.add(role); + + role = new Role(); + role.setName(domainName + ":role.readers"); + members = new ArrayList<>(); + members.add("user_domain.user100"); + members.add("user_domain.user101"); + role.setMembers(members); + roles.add(role); + + List policies = new ArrayList<>(); + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(providerDomain + ":role.tenant.readers"); + assertion.setAction("assume_role"); + assertion.setRole(domainName + ":role.readers"); + + List assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(domainName + ":policy.tenancy.readers"); + policies.add(policy); + + ServiceIdentity service = new ServiceIdentity(); + service.setName(domainName + ".storage"); + setServicePublicKey(service, "0", "abcdefgh"); + + List hosts = new ArrayList<>(); + hosts.add("host1"); + service.setHosts(hosts); + + List services = new ArrayList<>(); + services.add(service); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain(domainName); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), pkey)); + signedPolicies.setKeyId("0"); + + DomainData domain = new DomainData(); + domain.setName(domainName); + domain.setRoles(roles); + domain.setServices(services); + domain.setPolicies(signedPolicies); + + signedDomain.setDomain(domain); + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), pkey)); + signedDomain.setKeyId("0"); + + return signedDomain; + } + + @Test + public void testProcessDomainSaveInStore() throws IOException { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + File file = new File("/tmp/zts_server_unit_tests/zts_root/coretech"); + assertTrue(file.exists()); + + Path path = Paths.get(file.toURI()); + SignedDomain signedDomain2 = JSON.fromBytes(Files.readAllBytes(path), SignedDomain.class); + assertEquals(signedDomain2.getDomain().getName(), "coretech"); + + assertNotNull(store.getDomainData("coretech")); + } + + @Test + public void testProcessDomainNotSaveInStore() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, false); + + File file = new File("/tmp/zts_server_unit_tests/zts_root/coretech"); + assertFalse(file.exists()); + + assertNotNull(store.getDomainData("coretech")); + } + + @Test + public void testProcessLocalDomain() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + store = new DataStore(clogStore, null); + boolean result = store.processLocalDomain("coretech"); + assertTrue(result); + assertNotNull(store.getDomainData("coretech")); + } + + @Test + public void testProcessLocalDomainInvalidFile() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + boolean result = store.processLocalDomain("coretech"); + assertFalse(result); + } + + @Test + public void testProcessLocalDomains() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore setupStore = new DataStore(clogStore, null); + setupStore.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + setupStore.processDomain(signedDomain, true); + + signedDomain = createSignedDomain("sports", "weather"); + setupStore.processDomain(signedDomain, true); + + DataStore store = new DataStore(clogStore, null); + + List list = new ArrayList<>(); + list.add("coretech"); + list.add("sports"); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(list); + + boolean result = store.processLocalDomains(list); + assertTrue(result); + + assertNotNull(store.getDomainData("coretech")); + assertNotNull(store.getDomainData("sports")); + } + + @Test + public void testProcessLocalDomainsZMSDomainListNull() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore setupStore = new DataStore(clogStore, null); + setupStore.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + setupStore.processDomain(signedDomain, true); + + signedDomain = createSignedDomain("sports", "weather"); + setupStore.processDomain(signedDomain, true); + + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(null); + + List list = new ArrayList<>(); + list.add("coretech"); + list.add("sports"); + + boolean result = store.processLocalDomains(list); + assertFalse(result); + } + + @Test + public void testProcessLocalDomainsDeletedDomain() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore setupStore = new DataStore(clogStore, null); + setupStore.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + setupStore.processDomain(signedDomain, true); + + signedDomain = createSignedDomain("sports", "weather"); + setupStore.processDomain(signedDomain, true); + + DataStore store = new DataStore(clogStore, null); + List zmsList = new ArrayList<>(); + zmsList.add("coretech"); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(zmsList); + + List list = new ArrayList<>(); + list.add("coretech"); + list.add("sports"); + + boolean result = store.processLocalDomains(list); + assertTrue(result); + + assertNotNull(store.getDomainData("coretech")); + assertNull(store.getDomainData("sports")); + File file = new File("/tmp/zts_server_unit_tests/zts_root/sports"); + assertFalse(file.exists()); + } + + @Test + public void testProcessLocalDomainsInvalidLocalDomain() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore setupStore = new DataStore(clogStore, null); + setupStore.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + setupStore.processDomain(signedDomain, true); + + signedDomain = createSignedDomain("sports", "weather"); + setupStore.processDomain(signedDomain, true); + + DataStore store = new DataStore(clogStore, null); + List zmsList = new ArrayList<>(); + zmsList.add("coretech"); + zmsList.add("sports"); + zmsList.add("invalid"); + ((MockZMSFileChangeLogStore) store.changeLogStore).setDomainList(zmsList); + + List list = new ArrayList<>(); + list.add("coretech"); + list.add("sports"); + list.add("invalid"); + + boolean result = store.processLocalDomains(list); + assertFalse(result); + } + + @Test + public void testProcessSignedDomains() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List list = new ArrayList<>(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + list.add(signedDomain); + + signedDomain = createSignedDomain("sports", "weather"); + list.add(signedDomain); + + SignedDomains signedDomains = new SignedDomains(); + signedDomains.setDomains(list); + + boolean result = store.processSignedDomains(signedDomains); + assertTrue(result); + assertNotNull(store.getDomainData("coretech")); + assertNotNull(store.getDomainData("sports")); + } + + @Test + public void testProcessSignedDomainsNullList() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + boolean result = store.processSignedDomains(null); + assertTrue(result); + } + + @Test + public void testProcessSignedDomainsEmptyList() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + SignedDomains signedDomains = new SignedDomains(); + signedDomains.setDomains(null); + + boolean result = store.processSignedDomains(signedDomains); + assertTrue(result); + } + + @Test + public void testProcessSignedDomainsInvalidDomain() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List list = new ArrayList<>(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + list.add(signedDomain); + + signedDomain = createSignedDomain("sports", "weather"); + signedDomain.setSignature("Invalid0"); + list.add(signedDomain); + + SignedDomains signedDomains = new SignedDomains(); + signedDomains.setDomains(list); + + boolean result = store.processSignedDomains(signedDomains); + assertFalse(result); + } + + private DataCache createDataCache(String domainName) { + + DataCache dataCache = new DataCache(); + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName(domainName + ":role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user1"); + role.setMembers(members); + roles.add(role); + dataCache.processRole(role); + + role = new Role(); + role.setName(domainName + ":role.readers"); + members = new ArrayList<>(); + members.add("user_domain.user1"); + members.add("user_domain.user2"); + role.setMembers(members); + roles.add(role); + dataCache.processRole(role); + + role = new Role(); + role.setName(domainName + ":role.writers"); + role.setTrust(domainName + "Trust"); + roles.add(role); + dataCache.processRole(role); + + DomainData domainData = new DomainData(); + domainData.setName(domainName); + domainData.setRoles(roles); + + dataCache.setDomainData(domainData); + return dataCache; + } + + @Test + public void testProcessTrustedDomainDataNull() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String identity = "user_domain.user1"; + String role = "coretech:role.admin"; + + Set trustedResources = new HashSet<>(); + trustedResources.add("coretech:role.admin"); + trustedResources.add("coretech:role.readers"); + + store.processTrustedDomain(null, identity, prefix, role, trustedResources, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testProcessTrustedDomainResourcesNull() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + DataCache dataCache = createDataCache("coretech"); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String identity = "user_domain.user1"; + String role = "coretech:role.admin"; + + store.processTrustedDomain(dataCache, identity, prefix, role, null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testProcessTrustedDomainMemberRolesNull() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + DataCache dataCache = createDataCache("coretech"); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String identity = "user_domain.user3"; + String role = "coretech:role.admin"; + + Set trustedResources = new HashSet<>(); + trustedResources.add("coretech:role.admin"); + trustedResources.add("coretech:role.readers"); + + store.processTrustedDomain(dataCache, identity, prefix, role, trustedResources, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testProcessTrustedDomainNoRole() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + DataCache dataCache = createDataCache("coretech"); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String identity = "user_domain.user1"; + + Set trustedResources = new HashSet<>(); + trustedResources.add("coretech:role.admin"); + trustedResources.add("coretech:role.readers"); + + store.processTrustedDomain(dataCache, identity, prefix, null, trustedResources, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 2); + assertTrue(accessibleRoles.contains("admin")); + assertTrue(accessibleRoles.contains("readers")); + } + + @Test + public void testProcessTrustedDomainMemberRoleNotValid() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + DataCache dataCache = createDataCache("coretech"); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String identity = "user_domain.user1"; + String role = "coretech:role.writers"; /* invalid role causing no match */ + + Set trustedResources = new HashSet<>(); + trustedResources.add("coretech:role.admin"); + trustedResources.add("coretech:role.readers"); + + store.processTrustedDomain(dataCache, identity, prefix, role, trustedResources, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testProcessTrustedDomainRoleValid() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + DataCache dataCache = createDataCache("coretech"); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String identity = "user_domain.user1"; + String role = "coretech:role.admin"; + + Set trustedResources = new HashSet<>(); + trustedResources.add("coretech:role.admin"); + trustedResources.add("coretech:role.readers"); + + store.processTrustedDomain(dataCache, identity, prefix, role, trustedResources, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 1); + assertTrue(accessibleRoles.contains("admin")); + } + + @Test + public void testProcessTrustedDomainRoleInvalid() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + DataCache dataCache = createDataCache("coretech"); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech2" + ROLE_POSTFIX; /* invalid prefix to cause no match */ + String identity = "user_domain.user1"; + String role = "coretech:role.readers"; + + Set trustedResources = new HashSet<>(); + trustedResources.add("coretech:role.admin"); + trustedResources.add("coretech:role.readers"); + + store.processTrustedDomain(dataCache, identity, prefix, role, trustedResources, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testProcessStandardMembershipMemberRolesNull() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + + store.processStandardMembership(null, prefix, null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testProcessStandardMembershipRoleCheckNull() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + + Set memberRoles = new HashSet<>(); + memberRoles.add("coretech:role.admin"); + memberRoles.add("coretech:role.readers"); + + store.processStandardMembership(memberRoles, prefix, null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 2); + assertTrue(accessibleRoles.contains("admin")); + assertTrue(accessibleRoles.contains("readers")); + } + + @Test + public void testProcessStandardMembershipRoleValid() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String roleCheck = "coretech:role.admin"; + + Set memberRoles = new HashSet<>(); + memberRoles.add("coretech:role.admin"); + memberRoles.add("coretech:role.readers"); + + store.processStandardMembership(memberRoles, prefix, roleCheck, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 1); + } + + @Test + public void testProcessStandardMembershipRoleSuffixValid() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String roleCheck = "admin"; + + Set memberRoles = new HashSet<>(); + memberRoles.add("coretech:role.admin"); + memberRoles.add("coretech:role.readers"); + + store.processStandardMembership(memberRoles, prefix, roleCheck, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 1); + } + + @Test + public void testProcessStandardMembershipRoleInvalid() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech2" + ROLE_POSTFIX; /* invalid prefix causing no match */ + String roleCheck = "coretech:role.admin"; + + Set memberRoles = new HashSet<>(); + memberRoles.add("coretech:role.admin"); + memberRoles.add("coretech:role.readers"); + + store.processStandardMembership(memberRoles, prefix, roleCheck, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testProcessStandardMembershipRoleSuffixInValid() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String roleCheck = "2admin"; + + Set memberRoles = new HashSet<>(); + memberRoles.add("coretech:role.admin"); + memberRoles.add("coretech:role.readers"); + + store.processStandardMembership(memberRoles, prefix, roleCheck, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testProcessTrustMembershipNoTrustDomainMatch() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + signedDomain = createTenantSignedDomain("weather", "coretech"); + store.processDomain(signedDomain, true); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String identity = "user_domain.user100"; + + store.processTrustMembership(store.getCacheStore().getIfPresent("coretech"), identity, + prefix, null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 1); + assertTrue(accessibleRoles.contains("tenant.readers")); + } + + @Test + public void testProcessTrustMembershipNoTrustDomainMatchRoleCheck() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + signedDomain = createTenantSignedDomain("weather", "coretech"); + store.processDomain(signedDomain, true); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String identity = "user_domain.user100"; + + store.processTrustMembership(store.getCacheStore().getIfPresent("coretech"), identity, prefix, + "coretech:role.tenant.readers", accessibleRoles, false); + assertEquals(accessibleRoles.size(), 1); + assertTrue(accessibleRoles.contains("tenant.readers")); + } + + @Test + public void testProcessTrustMembershipNoTrustDomainNoMatch() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + signedDomain = createTenantSignedDomain("weather", "coretech"); + store.processDomain(signedDomain, true); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String identity = "user_domain.user400"; + + store.processTrustMembership(store.getCacheStore().getIfPresent("coretech"), identity, + prefix, null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testProcessDomainUpdatesFromZMSFailure() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).lastModTime = "2014-01-01T12:00:00"; + ((MockZMSFileChangeLogStore) store.changeLogStore).setSignedDomains(null); + + boolean result = store.processDomainUpdates(); + assertFalse(result); + } + + @Test + public void testProcessDomainUpdatesFromZMSNoTagHeader() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setTagHeader(null); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + + List domains = new ArrayList<>(); + domains.add(signedDomain); + + SignedDomains signedDomains = new SignedDomains(); + signedDomains.setDomains(domains); + + ((MockZMSFileChangeLogStore) store.changeLogStore).setSignedDomains(signedDomains); + + boolean result = store.processDomainUpdates(); + assertFalse(result); + } + + @Test + public void testProcessDomainUpdatesFromZMSInvalidSignedDomain() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setTagHeader("2014-01-01T12:00:00"); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + signedDomain.setSignature("ABCD"); /* invalidate the signature */ + + List domains = new ArrayList<>(); + domains.add(signedDomain); + + SignedDomains signedDomains = new SignedDomains(); + signedDomains.setDomains(domains); + + ((MockZMSFileChangeLogStore) store.changeLogStore).setSignedDomains(signedDomains); + + boolean result = store.processDomainUpdates(); + assertFalse(result); + } + + @Test + public void testProcessDomainUpdatesFromZMS() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setTagHeader("2014-01-01T12:00:00"); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + List domains = new ArrayList<>(); + + /* we're going to create a new domain */ + + signedDomain = createSignedDomain("sports", "weather"); + domains.add(signedDomain); + + /* we're going to update the coretech domain and set new roles */ + + signedDomain = createSignedDomain("coretech", "weather"); + + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user8"); + role.setMembers(members); + + List roles = new ArrayList<>(); + roles.add(role); + signedDomain.getDomain().setRoles(roles); + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(signedDomain.getDomain()), pkey)); + domains.add(signedDomain); + + SignedDomains signedDomains = new SignedDomains(); + signedDomains.setDomains(domains); + + ((MockZMSFileChangeLogStore) store.changeLogStore).setSignedDomains(signedDomains); + + boolean result = store.processDomainUpdates(); + assertTrue(result); + + List accessibleRoles = new ArrayList<>(); + DataCache data = store.getDataCache("coretech"); + store.getAccessibleRoles(data, "coretech", "user_domain.user1", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + + accessibleRoles = new ArrayList<>(); + store.getAccessibleRoles(data, "coretech", "user_domain.user8", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 1); + assertTrue(accessibleRoles.contains("admin")); + + accessibleRoles = new ArrayList<>(); + data = store.getDataCache("sports"); + store.getAccessibleRoles(data, "sports", "user_domain.user", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 2); + assertTrue(accessibleRoles.contains("admin")); + assertTrue(accessibleRoles.contains("writers")); + } + + @Test + public void testProcessDomainUpdatesFromZMSWithUpdater() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + ((MockZMSFileChangeLogStore) store.changeLogStore).setTagHeader("2014-01-01T12:00:00"); + store.loadZMSPublicKeys(); + + SignedDomain signedDomain = createSignedDomain("coretech", "weather"); + store.processDomain(signedDomain, true); + + List domains = new ArrayList<>(); + + /* we're going to create a new domain */ + + signedDomain = createSignedDomain("sports", "weather"); + domains.add(signedDomain); + + /* we're going to update the coretech domain and set new roles */ + + signedDomain = createSignedDomain("coretech", "weather"); + + Role role = new Role(); + role.setName("coretech:role.admin"); + List members = new ArrayList<>(); + members.add("user_domain.user8"); + role.setMembers(members); + + List roles = new ArrayList<>(); + roles.add(role); + signedDomain.getDomain().setRoles(roles); + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(signedDomain.getDomain()), pkey)); + domains.add(signedDomain); + + SignedDomains signedDomains = new SignedDomains(); + signedDomains.setDomains(domains); + + ((MockZMSFileChangeLogStore) store.changeLogStore).setSignedDomains(signedDomains); + + store.lastDeleteRunTime = System.currentTimeMillis() - 24 * 60 * 60; + DataUpdater updater = store.new DataUpdater(); + updater.run(); + + List accessibleRoles = new ArrayList<>(); + DataCache data = store.getDataCache("coretech"); + store.getAccessibleRoles(data, "coretech", "user_domain.user1", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + + accessibleRoles = new ArrayList<>(); + store.getAccessibleRoles(data, "coretech", "user_domain.user8", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 1); + assertTrue(accessibleRoles.contains("admin")); + + accessibleRoles = new ArrayList<>(); + data = store.getDataCache("sports"); + store.getAccessibleRoles(data, "sports", "user_domain.user", null, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 2); + assertTrue(accessibleRoles.contains("admin")); + assertTrue(accessibleRoles.contains("writers")); + } + + @Test + public void testRoleCheckValueRoleNull() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + assertNull(store.roleCheckValue(null, "coretech:role.")); + } + + @Test + public void testRoleCheckValuePrefixStart() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + String roleCheck = store.roleCheckValue("coretech:role.readers", "coretech:role."); + assertEquals(roleCheck, "coretech:role.readers"); + } + + @Test + public void testRoleCheckValueNoPrefixMatch() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + String roleCheck = store.roleCheckValue("readers", "coretech:role."); + assertEquals(roleCheck, "coretech:role.readers"); + } + + @Test + public void testRoleMatchInSetPlain() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + Set checkSet = new HashSet<>(); + checkSet.add("writers"); + checkSet.add("readers"); + + assertTrue(store.roleMatchInSet("writers", checkSet)); + assertTrue(store.roleMatchInSet("readers", checkSet)); + assertFalse(store.roleMatchInSet("admin", checkSet)); + assertFalse(store.roleMatchInSet("testwriters", checkSet)); + assertFalse(store.roleMatchInSet("writerstest", checkSet)); + } + + @Test + public void testRoleMatchInSetRegex() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + Set checkSet = new HashSet<>(); + checkSet.add("coretech:role.readers"); + checkSet.add("coretech:role.writers"); + checkSet.add("*:role.update"); + checkSet.add("weather:role.*"); + + assertTrue(store.roleMatchInSet("coretech:role.readers", checkSet)); + assertTrue(store.roleMatchInSet("coretech:role.writers", checkSet)); + assertTrue(store.roleMatchInSet("sports:role.update", checkSet)); + assertTrue(store.roleMatchInSet("weather:role.update", checkSet)); + assertFalse(store.roleMatchInSet("coretech:role.admin", checkSet)); + } + + @Test + public void testProcessSingleTrustedDomainRoleNoMatchInSet() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String role = "coretech:role.writers"; /* invalid role causing no match */ + + Set memberRoles = new HashSet<>(); + memberRoles.add("coretech:role.admin"); + memberRoles.add("coretech:role.readers"); + + store.processSingleTrustedDomainRole(role, prefix, null, memberRoles, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testProcessSingleTrustedDomainRoleAddRoleTrue() { + + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech" + ROLE_POSTFIX; + String role = "coretech:role.readers"; /* invalid role causing no match */ + + Set memberRoles = new HashSet<>(); + memberRoles.add("coretech:role.admin"); + memberRoles.add("coretech:role.readers"); + + store.processSingleTrustedDomainRole(role, prefix, null, memberRoles, accessibleRoles, false); + assertTrue(accessibleRoles.contains("readers")); + } + + @Test + public void testProcessSingleTrustedDomainRoleAddRoleFalse() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + + List accessibleRoles = new ArrayList<>(); + String prefix = "coretech2" + ROLE_POSTFIX; + String role = "coretech:role.readers"; /* invalid role causing no match */ + + Set memberRoles = new HashSet<>(); + memberRoles.add("coretech:role.admin"); + memberRoles.add("coretech:role.readers"); + + store.processSingleTrustedDomainRole(role, prefix, null, memberRoles, accessibleRoles, false); + assertEquals(accessibleRoles.size(), 0); + } + + @Test + public void testValidDomainListResponseEmpty() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + Set domainList = new HashSet<>(); + assertFalse(store.validDomainListResponse(domainList)); + } + + @Test + public void testValidDomainListResponse() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + Set domainList = new HashSet<>(); + domainList.add("sys.auth"); + domainList.add("coretech"); + domainList.add(userDomain); + assertTrue(store.validDomainListResponse(domainList)); + } + + @Test + public void testValidDomainListResponseNoSysAuth() { + ChangeLogStore clogStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + pkey, "0"); + DataStore store = new DataStore(clogStore, null); + Set domainList = new HashSet<>(); + domainList.add(userDomain); + domainList.add("coretech"); + assertFalse(store.validDomainListResponse(domainList)); + } +} \ No newline at end of file diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/store/MockCloudStore.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/MockCloudStore.java new file mode 100644 index 00000000000..03560b2aaab --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/MockCloudStore.java @@ -0,0 +1,148 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store; + +import org.mockito.Mockito; + +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; +import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; +import com.amazonaws.services.securitytoken.model.AssumeRoleResult; +import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest; +import com.amazonaws.services.securitytoken.model.GetCallerIdentityResult; +import com.yahoo.athenz.zts.AWSInstanceInformation; +import com.yahoo.athenz.zts.AWSTemporaryCredentials; +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.store.CloudStore; + +public class MockCloudStore extends CloudStore { + + String account = null; + String roleName = null; + String principal = null; + boolean skipSigCheck = false; + boolean returnNullClient = false; + boolean returnSuperAWSRole = false; + int identityCheck = 0; // 0 call super, 1 - true, -1 false + private AssumeRoleResult assumeRoleResult = null; + private GetCallerIdentityResult callerIdentityResult = null; + + public MockCloudStore() { + super(null); + } + + public MockCloudStore(CertSigner certSigner) { + super(certSigner); + } + + @Override + public + boolean isAwsEnabled() { + return true; + } + + public void setMockFields(String account, String roleName, String principal) { + this.account = account; + this.roleName = roleName; + this.principal = principal; + } + + public void skipDocumentSignatureCheck(boolean skipCheck) { + skipSigCheck = skipCheck; + } + + public void setIdentityCheckResult(int idCheck) { + identityCheck = idCheck; + } + + @Override + public + boolean verifyInstanceIdentity(AWSInstanceInformation info) { + boolean result = false; + switch (identityCheck) { + case 0: + result = super.verifyInstanceIdentity(info); + break; + case 1: + result = true; + break; + case -1: + result = false; + break; + } + return result; + } + + @Override + public + boolean validateInstanceDocument(String document, String signature) { + if (skipSigCheck) { + return true; + } + return super.validateInstanceDocument(document, signature); + } + + void setAssumeRoleResult(AssumeRoleResult assumeRoleResult) { + this.assumeRoleResult = assumeRoleResult; + } + + void setGetCallerIdentityResult(GetCallerIdentityResult callerIdentityResult) { + this.callerIdentityResult = callerIdentityResult; + } + + void setReturnNullClient(boolean returnNullClient) { + this.returnNullClient = returnNullClient; + } + + @Override + AWSSecurityTokenServiceClient getTokenServiceClient() { + AWSSecurityTokenServiceClient client = Mockito.mock(AWSSecurityTokenServiceClient.class); + Mockito.when(client.assumeRole(Mockito.any(AssumeRoleRequest.class))).thenReturn(assumeRoleResult); + Mockito.when(client.getCallerIdentity(Mockito.any(GetCallerIdentityRequest.class))).thenReturn(callerIdentityResult); + return client; + } + + @Override + AWSSecurityTokenServiceClient getInstanceClient(AWSInstanceInformation info) { + if (returnNullClient) { + return null; + } else { + AWSSecurityTokenServiceClient client = Mockito.mock(AWSSecurityTokenServiceClient.class); + Mockito.when(client.assumeRole(Mockito.any(AssumeRoleRequest.class))).thenReturn(assumeRoleResult); + Mockito.when(client.getCallerIdentity(Mockito.any(GetCallerIdentityRequest.class))).thenReturn(callerIdentityResult); + return client; + } + } + + void setAssumeAWSRole(boolean returnSuperAWSRole) { + this.returnSuperAWSRole = returnSuperAWSRole; + } + + @Override + public AWSTemporaryCredentials assumeAWSRole(String account, String roleName, String principal) { + + if (!returnSuperAWSRole) { + AWSTemporaryCredentials tempCreds = null; + if (this.account.equals(account) && this.roleName.equals(roleName) + && this.principal.equals(principal)) { + tempCreds = new AWSTemporaryCredentials(); + } + + return tempCreds; + } else { + return super.assumeAWSRole(account, roleName, principal); + } + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/MockZMSFileChangeLogStore.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/MockZMSFileChangeLogStore.java new file mode 100644 index 00000000000..4970b85b32e --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/MockZMSFileChangeLogStore.java @@ -0,0 +1,104 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.file; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.security.PrivateKey; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.yahoo.athenz.zms.DomainList; +import com.yahoo.athenz.zms.SignedDomains; +import com.yahoo.athenz.zms.ZMSClient; +import com.yahoo.athenz.zms.ZMSClientException; +import com.yahoo.athenz.zts.store.file.ZMSFileChangeLogStore; + +public class MockZMSFileChangeLogStore extends ZMSFileChangeLogStore { + + ZMSClient zms = null; + DomainList domList = null; + String tagHeader = null; + String userDomain; + + private static final String ZMS_USER_DOMAIN = "yahoo.zms.user_domain"; + + public MockZMSFileChangeLogStore(String rootDirectory, PrivateKey privateKey, String privateKeyId) { + + super(rootDirectory, privateKey, privateKeyId); + zms = mock(ZMSClient.class); + + // setup some default values to return when the store is initialized + // we're going to return on domain for local list and then another + // for server list - thus ending up with initialized store with no domains + + userDomain = System.getProperty(ZMS_USER_DOMAIN, "user"); + + DomainList localDomainList = new DomainList(); + List localDomains = new ArrayList<>(); + localDomains.add(userDomain); + localDomainList.setNames(localDomains); + + DomainList serverDomainList = new DomainList(); + List serverDomains = new ArrayList<>(); + serverDomains.add("sys"); + serverDomainList.setNames(serverDomains); + + tagHeader = "2014-01-01T12:00:00"; + when(zms.getDomainList()).thenReturn(localDomainList).thenReturn(serverDomainList); + + } + + @Override + public ZMSClient getZMSClient() { + return zms; + } + + public void setDomainList(List domains) { + if (domains != null) { + domList = new DomainList(); + domList.setNames(domains); + when(zms.getDomainList()).thenReturn(domList); + } else { + when(zms.getDomainList()).thenThrow(new ZMSClientException(500, "Invalid request")); + } + } + + @SuppressWarnings("unchecked") + public void setSignedDomains(SignedDomains signedDomains) { + if (signedDomains != null) { + when(zms.getSignedDomains(any(String.class), any(String.class), any(String.class), any(Map.class))).thenReturn(signedDomains); + } else { + when(zms.getSignedDomains(any(String.class), any(String.class), any(String.class), any(Map.class))).thenThrow(new ZMSClientException(500, "Invalid request")); + } + } + + public void setTagHeader(String tagHeader) { + this.tagHeader = tagHeader; + } + + @Override + public String retrieveTagHeader(Map> responseHeaders) { + if (tagHeader == null) { + return super.retrieveTagHeader(responseHeaders); + } else { + return tagHeader; + } + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/MockZMSFileChangeLogStoreFactory.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/MockZMSFileChangeLogStoreFactory.java new file mode 100644 index 00000000000..91ea96f2461 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/MockZMSFileChangeLogStoreFactory.java @@ -0,0 +1,31 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.file; + +import java.security.PrivateKey; + +import com.yahoo.athenz.zts.store.ChangeLogStore; +import com.yahoo.athenz.zts.store.ChangeLogStoreFactory; +import com.yahoo.athenz.zts.store.CloudStore; + +public class MockZMSFileChangeLogStoreFactory implements ChangeLogStoreFactory { + + @Override + public ChangeLogStore create(String ztsHomeDir, PrivateKey privateKey, + String privateKeyId, CloudStore cloudStore) { + return new MockZMSFileChangeLogStore(ztsHomeDir, privateKey, privateKeyId); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreFactoryTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreFactoryTest.java new file mode 100644 index 00000000000..5f3a39c1b1a --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreFactoryTest.java @@ -0,0 +1,46 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.file; + +import static org.testng.Assert.assertNotNull; + +import java.io.File; +import java.security.PrivateKey; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.zts.ZTSConsts; +import com.yahoo.athenz.zts.store.ChangeLogStore; +import com.yahoo.athenz.zts.store.file.ZMSFileChangeLogStoreFactory; + +public class ZMSFileChangeLogStoreFactoryTest { + + static final String ZTS_DATA_STORE_PATH = "/tmp/zts_server_unit_tests/zts_root"; + + @Test + public void testCreateStore() { + + String privKeyName = System.getProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY); + File privKeyFile = new File(privKeyName); + String privKey = Crypto.encodedFile(privKeyFile); + PrivateKey pkey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + + ZMSFileChangeLogStoreFactory factory = new ZMSFileChangeLogStoreFactory(); + ChangeLogStore store = factory.create(ZTS_DATA_STORE_PATH, pkey, "0", null); + assertNotNull(store); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreTest.java new file mode 100644 index 00000000000..e9b6e7474b9 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/file/ZMSFileChangeLogStoreTest.java @@ -0,0 +1,219 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.file; + +import static org.testng.Assert.*; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.yahoo.athenz.zts.store.file.ZMSFileChangeLogStore; +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Struct; + +public class ZMSFileChangeLogStoreTest { + + private final String FSTORE_PATH = "/tmp/zts_file_store_unit_test"; + + private void touch(String fname) throws IOException { + + File file = new File(fname); + + if (!file.exists()) { + new FileOutputStream(file).close(); + } + + file.setLastModified(System.currentTimeMillis()); + } + + @BeforeMethod + public void setup() { + ZMSFileChangeLogStore.deleteDirectory(new File(FSTORE_PATH)); + } + + @AfterMethod + public void shutdown() { + ZMSFileChangeLogStore.deleteDirectory(new File(FSTORE_PATH)); + } + + @Test + public void FileStructStoreValidDir() { + + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(FSTORE_PATH, null, null); + assertNotNull(fstore); + } + + @Test + public void FileStructStoreInvalid() { + + try { + File rootDir = new File(FSTORE_PATH); + rootDir.mkdirs(); + + String fpath = FSTORE_PATH + "/zts_file.tmp"; + touch(fpath); + + @SuppressWarnings("unused") + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(fpath, null, null); + fail(); + } catch (RuntimeException | IOException ex) { + assertTrue(true); + } + } + + @Test + public void getNonExistent() { + + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(FSTORE_PATH, null, null); + Struct st = fstore.get("NotExistent", Struct.class); + assertNull(st); + } + + @Test + public void getExistent() { + + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(FSTORE_PATH, null, null); + Struct data = new Struct(); + data.put("key", "val1"); + fstore.put("test1", JSON.bytes(data)); + + Struct st = fstore.get("test1", Struct.class); + assertNotNull(st); + assertEquals(st.get("key"), "val1"); + } + + @Test + public void deleteExistent() { + + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(FSTORE_PATH, null, null); + Struct data = new Struct(); + data.put("key", "val1"); + fstore.put("test1", JSON.bytes(data)); + + fstore.delete("test1"); + Struct st = fstore.get("test1", Struct.class); + assertNull(st); + } + + @Test + public void deleteNonExistent() { + + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(FSTORE_PATH, null, null); + + fstore.delete("test1"); + assertTrue(true); + } + + @Test + public void scanEmpty() { + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(FSTORE_PATH, null, null); + List ls = fstore.scan(); + assertEquals(ls.size(), 0); + } + + @Test + public void scanSingle() { + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(FSTORE_PATH, null, null); + Struct data = new Struct(); + data.put("key", "val1"); + fstore.put("test1", JSON.bytes(data)); + + List ls = fstore.scan(); + assertEquals(ls.size(), 1); + assertTrue(ls.contains("test1")); + } + + @Test + public void scanMultiple() { + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(FSTORE_PATH, null, null); + + Struct data = new Struct(); + data.put("key", "val1"); + fstore.put("test1", JSON.bytes(data)); + + data = new Struct(); + data.put("key", "val1"); + fstore.put("test2", JSON.bytes(data)); + + data = new Struct(); + data.put("key", "val1"); + fstore.put("test3", JSON.bytes(data)); + + List ls = fstore.scan(); + assertEquals(ls.size(), 3); + assertTrue(ls.contains("test1")); + assertTrue(ls.contains("test2")); + assertTrue(ls.contains("test3")); + } + + @Test + public void scanHidden() { + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(FSTORE_PATH, null, null); + + Struct data = new Struct(); + data.put("key", "val1"); + fstore.put("test1", JSON.bytes(data)); + + data = new Struct(); + data.put("key", "val1"); + fstore.put(".test2", JSON.bytes(data)); + + data = new Struct(); + data.put("key", "val1"); + fstore.put(".test3", JSON.bytes(data)); + + List ls = fstore.scan(); + assertEquals(ls.size(), 1); + assertTrue(ls.contains("test1")); + } + + @Test + public void scanDelete() { + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(FSTORE_PATH, null, null); + + Struct data = new Struct(); + data.put("key", "val1"); + fstore.put("test1", JSON.bytes(data)); + + data = new Struct(); + data.put("key", "val1"); + fstore.put("test2", JSON.bytes(data)); + + data = new Struct(); + data.put("key", "val1"); + fstore.put("test3", JSON.bytes(data)); + + fstore.delete("test2"); + + List ls = fstore.scan(); + assertEquals(ls.size(), 2); + assertTrue(ls.contains("test1")); + assertTrue(ls.contains("test3")); + } + + @Test + public void testFullRefreshSupport() { + + ZMSFileChangeLogStore fstore = new ZMSFileChangeLogStore(FSTORE_PATH, null, null); + assertTrue(fstore.supportsFullRefresh()); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/MockS3ChangeLogStore.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/MockS3ChangeLogStore.java new file mode 100644 index 00000000000..3a2cf2ddf66 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/MockS3ChangeLogStore.java @@ -0,0 +1,38 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.s3; + +import static org.mockito.Mockito.mock; + +import com.amazonaws.services.s3.AmazonS3; +import com.yahoo.athenz.zts.store.CloudStore; +import com.yahoo.athenz.zts.store.s3.S3ChangeLogStore; + +public class MockS3ChangeLogStore extends S3ChangeLogStore { + + AmazonS3 s3 = null; + + public MockS3ChangeLogStore(CloudStore cloudStore) { + super(cloudStore); + s3 = mock(AmazonS3.class); + } + + @Override + AmazonS3 getS3Client() { + return s3; + } + +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/MockS3ChangeLogStoreFactory.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/MockS3ChangeLogStoreFactory.java new file mode 100644 index 00000000000..c704e7bfc49 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/MockS3ChangeLogStoreFactory.java @@ -0,0 +1,31 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.s3; + +import java.security.PrivateKey; + +import com.yahoo.athenz.zts.store.ChangeLogStore; +import com.yahoo.athenz.zts.store.CloudStore; +import com.yahoo.athenz.zts.store.s3.S3ChangeLogStoreFactory; + +public class MockS3ChangeLogStoreFactory extends S3ChangeLogStoreFactory { + + @Override + public ChangeLogStore create(String ztsHomeDir, PrivateKey privateKey, + String privateKeyId, CloudStore cloudStore) { + return new MockS3ChangeLogStore(cloudStore); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreFactoryTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreFactoryTest.java new file mode 100644 index 00000000000..a85c26131ba --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreFactoryTest.java @@ -0,0 +1,33 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.s3; + +import org.testng.annotations.Test; + +import com.yahoo.athenz.zts.store.ChangeLogStore; +import com.yahoo.athenz.zts.store.s3.S3ChangeLogStoreFactory; + +import static org.testng.Assert.*; + +public class S3ChangeLogStoreFactoryTest { + + @Test + public void testCreateStore() { + S3ChangeLogStoreFactory factory = new S3ChangeLogStoreFactory(); + ChangeLogStore store = factory.create(null, null, null, null); + assertNotNull(store); + } +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreTest.java new file mode 100644 index 00000000000..cf0f8395303 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/store/s3/S3ChangeLogStoreTest.java @@ -0,0 +1,457 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.store.s3; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; +import static org.testng.Assert.*; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.apache.http.client.methods.HttpRequestBase; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.amazonaws.AmazonClientException; +import com.amazonaws.AmazonServiceException; +import com.amazonaws.services.s3.model.GetObjectRequest; +import com.amazonaws.services.s3.model.ListObjectsRequest; +import com.amazonaws.services.s3.model.ObjectListing; +import com.amazonaws.services.s3.model.S3Object; +import com.amazonaws.services.s3.model.S3ObjectInputStream; +import com.amazonaws.services.s3.model.S3ObjectSummary; +import com.yahoo.athenz.zms.DomainData; +import com.yahoo.athenz.zms.SignedDomain; +import com.yahoo.athenz.zms.SignedDomains; +import com.yahoo.athenz.zts.store.s3.S3ChangeLogStore; + +public class S3ChangeLogStoreTest { + + @BeforeMethod + public void setup() { + } + + @AfterMethod + public void shutdown() { + } + + @Test + public void testFullRefreshSupport() { + + S3ChangeLogStore store = new S3ChangeLogStore(null); + assertFalse(store.supportsFullRefresh()); + } + + @Test + public void testNoOpMethods() { + S3ChangeLogStore store = new S3ChangeLogStore(null); + store.removeLocalDomain("iaas.athenz"); + store.saveLocalDomain("iaas.athenz", null); + } + + @Test + public void testSetLastModificationTimestamp() { + S3ChangeLogStore store = new S3ChangeLogStore(null); + assertEquals(store.lastModTime, 0); + + store.setLastModificationTimestamp("12345"); + assertEquals(store.lastModTime, 12345); + + store.setLastModificationTimestamp(null); + assertEquals(store.lastModTime, 0); + } + + @Test + public void testListObjectsAllObjectsNoPage() { + + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + + ArrayList objectList = new ArrayList<>(); + S3ObjectSummary objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas"); + objectList.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas.athenz"); + objectList.add(objectSummary); + + ObjectListing objectListing = mock(ObjectListing.class); + when(objectListing.getObjectSummaries()).thenReturn(objectList); + when(objectListing.isTruncated()).thenReturn(false); + when(store.s3.listObjects(any(ListObjectsRequest.class))).thenReturn(objectListing); + + ArrayList domains = new ArrayList<>(); + store.listObjects(store.s3, domains, 0); + + assertEquals(domains.size(), 2); + assertTrue(domains.contains("iaas")); + assertTrue(domains.contains("iaas.athenz")); + } + + @Test + public void testListObjectsAllObjectsNoPageModTime() { + + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + + ArrayList objectList = new ArrayList<>(); + S3ObjectSummary objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas"); + objectSummary.setLastModified(new Date(100)); + objectList.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas.athenz"); + objectSummary.setLastModified(new Date(200)); + objectList.add(objectSummary); + + ObjectListing objectListing = mock(ObjectListing.class); + when(objectListing.getObjectSummaries()).thenReturn(objectList); + when(objectListing.isTruncated()).thenReturn(false); + when(store.s3.listObjects(any(ListObjectsRequest.class))).thenReturn(objectListing); + + ArrayList domains = new ArrayList<>(); + store.listObjects(store.s3, domains, (new Date(150)).getTime()); + + assertEquals(domains.size(), 1); + assertTrue(domains.contains("iaas.athenz")); + } + + @Test + public void testListObjectsAllObjectsMultiplePages() { + + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + + ArrayList objectList1 = new ArrayList<>(); + S3ObjectSummary objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas"); + objectList1.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas.athenz"); + objectList1.add(objectSummary); + + ArrayList objectList2 = new ArrayList<>(); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("cd"); + objectList2.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("cd.docker"); + objectList2.add(objectSummary); + + ArrayList objectList3 = new ArrayList<>(); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("platforms"); + objectList3.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("platforms.mh2"); + objectList3.add(objectSummary); + + ObjectListing objectListing = mock(ObjectListing.class); + when(objectListing.getObjectSummaries()) + .thenReturn(objectList1) + .thenReturn(objectList2) + .thenReturn(objectList3); + when(objectListing.isTruncated()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + when(store.s3.listObjects(any(ListObjectsRequest.class))).thenReturn(objectListing); + when(store.s3.listNextBatchOfObjects(any(ObjectListing.class))).thenReturn(objectListing); + + ArrayList domains = new ArrayList<>(); + store.listObjects(store.s3, domains, 0); + + assertEquals(domains.size(), 6); + assertTrue(domains.contains("iaas")); + assertTrue(domains.contains("iaas.athenz")); + assertTrue(domains.contains("cd")); + assertTrue(domains.contains("cd.docker")); + assertTrue(domains.contains("platforms")); + assertTrue(domains.contains("platforms.mh2")); + } + + @Test + public void testListObjectsAllObjectsMultiplePagesModTime() { + + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + + ArrayList objectList1 = new ArrayList<>(); + S3ObjectSummary objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas"); + objectSummary.setLastModified(new Date(100)); + objectList1.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas.athenz"); + objectSummary.setLastModified(new Date(100)); + objectList1.add(objectSummary); + + ArrayList objectList2 = new ArrayList<>(); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("cd"); + objectSummary.setLastModified(new Date(100)); + objectList2.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("cd.docker"); + objectSummary.setLastModified(new Date(200)); + objectList2.add(objectSummary); + + ArrayList objectList3 = new ArrayList<>(); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("platforms"); + objectSummary.setLastModified(new Date(200)); + objectList3.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("platforms.mh2"); + objectSummary.setLastModified(new Date(200)); + objectList3.add(objectSummary); + + ObjectListing objectListing = mock(ObjectListing.class); + when(objectListing.getObjectSummaries()) + .thenReturn(objectList1) + .thenReturn(objectList2) + .thenReturn(objectList3); + when(objectListing.isTruncated()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + when(store.s3.listObjects(any(ListObjectsRequest.class))).thenReturn(objectListing); + when(store.s3.listNextBatchOfObjects(any(ObjectListing.class))).thenReturn(objectListing); + + ArrayList domains = new ArrayList<>(); + store.listObjects(store.s3, domains, (new Date(150)).getTime()); + + assertEquals(domains.size(), 3); + assertTrue(domains.contains("cd.docker")); + assertTrue(domains.contains("platforms")); + assertTrue(domains.contains("platforms.mh2")); + } + + @Test + public void testGetLocalDomains() { + + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + ArrayList objectList = new ArrayList<>(); + S3ObjectSummary objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas"); + objectList.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas.athenz"); + objectList.add(objectSummary); + + ObjectListing objectListing = mock(ObjectListing.class); + when(objectListing.getObjectSummaries()).thenReturn(objectList); + when(objectListing.isTruncated()).thenReturn(false); + when(store.s3.listObjects(any(ListObjectsRequest.class))).thenReturn(objectListing); + + // verify that our last mod time is 0 before the call + + assertEquals(store.lastModTime, 0); + + // retrieve the list of domains + + List domains = store.getLocalDomainList(); + + assertEquals(domains.size(), 2); + assertTrue(domains.contains("iaas")); + assertTrue(domains.contains("iaas.athenz")); + + // also verify that last mod time is updated + + assertTrue(store.lastModTime > 0); + } + + @Test + public void testGetServerDomains() { + + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + ArrayList objectList = new ArrayList<>(); + S3ObjectSummary objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas"); + objectList.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas.athenz"); + objectList.add(objectSummary); + + ObjectListing objectListing = mock(ObjectListing.class); + when(objectListing.getObjectSummaries()).thenReturn(objectList); + when(objectListing.isTruncated()).thenReturn(false); + when(store.s3.listObjects(any(ListObjectsRequest.class))).thenReturn(objectListing); + + // verify that our last mod time is 0 before the call + + assertEquals(store.lastModTime, 0); + + // retrieve the list of domains + + Set domains = store.getServerDomainList(); + + assertEquals(domains.size(), 2); + assertTrue(domains.contains("iaas")); + assertTrue(domains.contains("iaas.athenz")); + + // also verify that last mod time is not updated + + assertEquals(store.lastModTime, 0); + } + + @Test + public void testGetSignedDomainNotFound() { + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + when(store.s3.getObject(any(GetObjectRequest.class))).thenReturn((S3Object) null); + + assertNull(store.getSignedDomain(store.s3, "iaas")); + } + + @Test + public void testGetSignedDomainClientException() { + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + + when(store.s3.getObject(any(GetObjectRequest.class))).thenThrow(new AmazonClientException("failed client operation")); + assertNull(store.getSignedDomain(store.s3, "iaas")); + } + + @Test + public void testGetSignedDomainServiceException() { + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + + when(store.s3.getObject(any(GetObjectRequest.class))).thenThrow(new AmazonServiceException("failed server operation")); + assertNull(store.getSignedDomain(store.s3, "iaas")); + } + + private class MockS3ObjectInputStream extends S3ObjectInputStream { + public MockS3ObjectInputStream(InputStream in, HttpRequestBase httpRequest) { + super(in, httpRequest); + } + } + + @Test + public void testGetSignedDomainInternal() throws IOException { + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + + InputStream is = new FileInputStream("src/test/resources/iaas.json"); + MockS3ObjectInputStream s3Is = new MockS3ObjectInputStream(is, null); + + S3Object object = mock(S3Object.class); + when(object.getObjectContent()).thenReturn(s3Is); + + when(store.s3.getObject(any(GetObjectRequest.class))).thenReturn(object); + + SignedDomain signedDomain = store.getSignedDomain(store.s3, "iaas"); + assertNotNull(signedDomain); + DomainData domainData = signedDomain.getDomain(); + assertNotNull(domainData); + assertEquals(domainData.getName(), "iaas"); + is.close(); + } + + @Test + public void testGetSignedDomain() throws IOException { + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + + InputStream is = new FileInputStream("src/test/resources/iaas.json"); + MockS3ObjectInputStream s3Is = new MockS3ObjectInputStream(is, null); + + S3Object object = mock(S3Object.class); + when(object.getObjectContent()).thenReturn(s3Is); + + when(store.s3.getObject(any(GetObjectRequest.class))).thenReturn(object); + + SignedDomain signedDomain = store.getSignedDomain("iaas"); + assertNotNull(signedDomain); + DomainData domainData = signedDomain.getDomain(); + assertNotNull(domainData); + assertEquals(domainData.getName(), "iaas"); + is.close(); + } + + @Test + public void testGetUpdatedSignedDomainsNoChanges() { + + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + + ArrayList objectList = new ArrayList<>(); + S3ObjectSummary objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas"); + objectSummary.setLastModified(new Date(100)); + objectList.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas.athenz"); + objectSummary.setLastModified(new Date(200)); + objectList.add(objectSummary); + + ObjectListing objectListing = mock(ObjectListing.class); + when(objectListing.getObjectSummaries()).thenReturn(objectList); + when(objectListing.isTruncated()).thenReturn(false); + when(store.s3.listObjects(any(ListObjectsRequest.class))).thenReturn(objectListing); + + // set the last modification time to not return any of the domains + store.lastModTime = (new Date(250)).getTime(); + + StringBuilder lastModTimeBuffer = new StringBuilder(512); + SignedDomains signedDomains = store.getUpdatedSignedDomains(lastModTimeBuffer); + assertTrue(lastModTimeBuffer.length() > 0); + assertEquals(signedDomains.getDomains().size(), 0); + } + + @Test + public void testGetUpdatedSignedDomainsWithChange() throws FileNotFoundException { + + MockS3ChangeLogStore store = new MockS3ChangeLogStore(null); + + ArrayList objectList = new ArrayList<>(); + S3ObjectSummary objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas"); + objectSummary.setLastModified(new Date(100)); + objectList.add(objectSummary); + objectSummary = new S3ObjectSummary(); + objectSummary.setKey("iaas.athenz"); + objectSummary.setLastModified(new Date(200)); + objectList.add(objectSummary); + + ObjectListing objectListing = mock(ObjectListing.class); + when(objectListing.getObjectSummaries()).thenReturn(objectList); + when(objectListing.isTruncated()).thenReturn(false); + when(store.s3.listObjects(any(ListObjectsRequest.class))).thenReturn(objectListing); + + InputStream is = new FileInputStream("src/test/resources/iaas.json"); + MockS3ObjectInputStream s3Is = new MockS3ObjectInputStream(is, null); + + S3Object object = mock(S3Object.class); + when(object.getObjectContent()).thenReturn(s3Is); + + when(store.s3.getObject(any(GetObjectRequest.class))).thenReturn(object); + + // set the last modification time to return one of the domains + store.lastModTime = (new Date(150)).getTime(); + + StringBuilder lastModTimeBuffer = new StringBuilder(512); + SignedDomains signedDomains = store.getUpdatedSignedDomains(lastModTimeBuffer); + assertTrue(lastModTimeBuffer.length() > 0); + + List domainList = signedDomains.getDomains(); + assertEquals(domainList.size(), 1); + + DomainData domainData = domainList.get(0).getDomain(); + assertNotNull(domainData); + assertEquals(domainData.getName(), "iaas"); + } + +} diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/utils/ZTSUtilsTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/utils/ZTSUtilsTest.java new file mode 100644 index 00000000000..7c66ef026a4 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/utils/ZTSUtilsTest.java @@ -0,0 +1,164 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zts.utils; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.mockito.Mockito; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import com.yahoo.athenz.zts.Identity; +import com.yahoo.athenz.zts.ZTSConsts; +import com.yahoo.athenz.zts.cert.CertSigner; +import com.yahoo.athenz.zts.utils.ZTSUtils; + +public class ZTSUtilsTest { + + @AfterMethod + public void cleanup() { + System.clearProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PATH); + System.clearProperty(ZTSConsts.ZTS_PROP_KEYSTORE_TYPE); + System.clearProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PASSWORD); + System.clearProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PATH); + System.clearProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_TYPE); + System.clearProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PASSWORD); + System.clearProperty(ZTSConsts.ZTS_PROP_KEYMANAGER_PASSWORD); + System.clearProperty(ZTSConsts.ZTS_PROP_EXCLUDED_CIPHER_SUITES); + System.clearProperty(ZTSConsts.ZTS_PROP_EXCLUDED_PROTOCOLS); + System.clearProperty(ZTSConsts.ZTS_PROP_WANT_CLIENT_CERT); + } + + @Test + public void testRetrieveConfigSetting() { + + System.setProperty("prop1", "1001"); + assertEquals(1001, ZTSUtils.retrieveConfigSetting("prop1", 99)); + assertEquals(99, ZTSUtils.retrieveConfigSetting("prop2", 99)); + + System.setProperty("prop1", "-101"); + assertEquals(99, ZTSUtils.retrieveConfigSetting("prop1", 99)); + + System.setProperty("prop1", "0"); + assertEquals(99, ZTSUtils.retrieveConfigSetting("prop1", 99)); + + System.setProperty("prop1", "abc"); + assertEquals(99, ZTSUtils.retrieveConfigSetting("prop1", 99)); + } + + @Test + public void testCreateSSLContextObject() { + + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PATH, "file:///tmp/keystore"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_TYPE, "PKCS12"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PATH, "file:///tmp/truststore"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYMANAGER_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_EXCLUDED_CIPHER_SUITES, ZTSUtils.ZTS_DEFAULT_EXCLUDED_CIPHER_SUITES); + System.setProperty(ZTSConsts.ZTS_PROP_EXCLUDED_PROTOCOLS, ZTSUtils.ZTS_DEFAULT_EXCLUDED_PROTOCOLS); + System.setProperty(ZTSConsts.ZTS_PROP_WANT_CLIENT_CERT, "true"); + + SslContextFactory sslContextFactory = ZTSUtils.createSSLContextObject(null); + assertNotNull(sslContextFactory); + assertEquals(sslContextFactory.getKeyStorePath(), "file:///tmp/keystore"); + assertEquals(sslContextFactory.getKeyStoreType(), "PKCS12"); + assertEquals(sslContextFactory.getTrustStoreResource().toString(), "file:///tmp/truststore"); + assertEquals(sslContextFactory.getTrustStoreType(), "PKCS12"); + assertEquals(sslContextFactory.getExcludeCipherSuites(), ZTSUtils.ZTS_DEFAULT_EXCLUDED_CIPHER_SUITES.split(",")); + assertEquals(sslContextFactory.getExcludeProtocols(), ZTSUtils.ZTS_DEFAULT_EXCLUDED_PROTOCOLS.split(",")); + assertTrue(sslContextFactory.getWantClientAuth()); + } + + @Test + public void testCreateSSLContextObjectNoValues() { + + SslContextFactory sslContextFactory = ZTSUtils.createSSLContextObject(null); + + assertNotNull(sslContextFactory); + assertFalse(sslContextFactory.getWantClientAuth()); + assertNull(sslContextFactory.getKeyStoreResource()); + // store type always defaults to PKCS12 + assertEquals(sslContextFactory.getKeyStoreType(), "PKCS12"); + assertNull(sslContextFactory.getTrustStore()); + // store type always defaults to PKCS12 + assertEquals(sslContextFactory.getTrustStoreType(), "PKCS12"); + } + + @Test + public void testCreateSSLContextObjectNoKeyStore() { + + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PATH, "file:///tmp/truststore"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_TYPE, "PKCS12"); + System.setProperty(ZTSConsts.ZTS_PROP_TRUSTSTORE_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYMANAGER_PASSWORD, "pass123"); + + SslContextFactory sslContextFactory = ZTSUtils.createSSLContextObject(null); + assertNotNull(sslContextFactory); + assertFalse(sslContextFactory.getWantClientAuth()); + assertNull(sslContextFactory.getKeyStoreResource()); + // store type always defaults to PKCS12 + assertEquals(sslContextFactory.getKeyStoreType(), "PKCS12"); + assertEquals(sslContextFactory.getTrustStoreResource().toString(), "file:///tmp/truststore"); + assertEquals(sslContextFactory.getTrustStoreType(), "PKCS12"); + } + + @Test + public void testCreateSSLContextObjectNoTrustStore() { + + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PATH, "file:///tmp/keystore"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_TYPE, "PKCS12"); + System.setProperty(ZTSConsts.ZTS_PROP_KEYSTORE_PASSWORD, "pass123"); + System.setProperty(ZTSConsts.ZTS_PROP_EXCLUDED_CIPHER_SUITES, ZTSUtils.ZTS_DEFAULT_EXCLUDED_CIPHER_SUITES); + System.setProperty(ZTSConsts.ZTS_PROP_EXCLUDED_PROTOCOLS, ZTSUtils.ZTS_DEFAULT_EXCLUDED_PROTOCOLS); + System.setProperty(ZTSConsts.ZTS_PROP_WANT_CLIENT_CERT, "true"); + + SslContextFactory sslContextFactory = ZTSUtils.createSSLContextObject(null); + assertNotNull(sslContextFactory); + assertTrue(sslContextFactory.getWantClientAuth()); + assertEquals(sslContextFactory.getKeyStorePath(), "file:///tmp/keystore"); + assertEquals(sslContextFactory.getKeyStoreType(), "PKCS12"); + assertNull(sslContextFactory.getTrustStoreResource()); + // store type always defaults to PKCS12 + assertEquals(sslContextFactory.getTrustStoreType(), "PKCS12"); + assertEquals(sslContextFactory.getExcludeCipherSuites(), ZTSUtils.ZTS_DEFAULT_EXCLUDED_CIPHER_SUITES.split(",")); + assertEquals(sslContextFactory.getExcludeProtocols(), ZTSUtils.ZTS_DEFAULT_EXCLUDED_PROTOCOLS.split(",")); + } + + @Test + public void testGenerateIdentityFailure() throws IOException { + + CertSigner certSigner = Mockito.mock(CertSigner.class); + Mockito.when(certSigner.generateX509Certificate(Mockito.anyString())).thenReturn(null); + + Path path = Paths.get("src/test/resources/valid.csr"); + String csr = new String(Files.readAllBytes(path)); + + Identity identity = ZTSUtils.generateIdentity(certSigner, csr, "iaas.athens.syncer", "pem"); + assertNull(identity); + } +} diff --git a/servers/zts/src/test/resources/athenz.conf b/servers/zts/src/test/resources/athenz.conf new file mode 100644 index 00000000000..ec0debdf29d --- /dev/null +++ b/servers/zts/src/test/resources/athenz.conf @@ -0,0 +1,20 @@ +{ +"zmsUrl": "http://localhost:10080/", +"ztsUrl": "http://localhost:8080/", +"ztsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + } +], +"zmsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + }, + { + "id": "zms.dev.0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFSXJ1ZTJtUkF1WkdmdEZyZmQ2RW55bEFCNnFpSgpUQXE0bE11TTQ0alZOVHNTb0JBVlAzYmVwRFZuWDdZOTZOT1JtMHdENE01RzVNTm44czBhUys1TzdnPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg--" + } +] +} diff --git a/servers/zts/src/test/resources/iaas.json b/servers/zts/src/test/resources/iaas.json new file mode 100644 index 00000000000..bb701e3dd9d --- /dev/null +++ b/servers/zts/src/test/resources/iaas.json @@ -0,0 +1 @@ +{"domain":{"roles":[{"modified":"2016-03-31T16:40:44.544Z","name":"iaas:role.admin","members":["yby.hga","yby.zms_test_admin"]}],"policies":{"contents":{"domain":"iaas","policies":[{"modified":"2016-03-31T16:40:44.546Z","assertions":[{"role":"iaas:role.admin","action":"*","effect":"ALLOW","resource":"iaas:*"}],"name":"iaas:policy.admin"}]},"keyId":"zms.dev.0","signature":"MEUCID0ciS7zBGIEJbUo2aIamnwcA4K_Sx4HtE1LZkPCtZKKAiEA9sAufsEnjODZM8U1p4EOqObJZ9L2Szna_qwsFtinm0U-"},"modified":"2016-03-31T16:40:45.128Z","services":[],"name":"iaas"},"keyId":"zms.dev.0","signature":"MEQCIBl9i0P.apXlpPJ7NCl1FMOHCHTPBgazwtHcEflDIIB1AiA3IUSh7CEDRUXM3PkjU8hBT6XNnXQRLT2ywi2Q0ZciMw--"} \ No newline at end of file diff --git a/servers/zts/src/test/resources/logback.xml b/servers/zts/src/test/resources/logback.xml new file mode 100644 index 00000000000..9e4d06ad246 --- /dev/null +++ b/servers/zts/src/test/resources/logback.xml @@ -0,0 +1,57 @@ + + + + + + + System.out + + >%d{HH:mm:ss.SSS} [%thread] %-5level %class - %msg%n + + + + + + + ${LOG_DIR}/server.log + true + + + ${LOG_DIR}/server.%d{yyyy-MM-dd}.log + 7 + true + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n + + + + + + + ${LOG_DIR}/audit.log + true + + + ${LOG_DIR}/audit.%d{yyyy-MM-dd}.log + 7 + true + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n + + + + + + + + + + + + + + diff --git a/servers/zts/src/test/resources/private_encrypted.key b/servers/zts/src/test/resources/private_encrypted.key new file mode 100644 index 00000000000..dbefd0227b0 --- /dev/null +++ b/servers/zts/src/test/resources/private_encrypted.key @@ -0,0 +1,10 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIBeTAbBgkqhkiG9w0BBQMwDgQITzfDOS9/ungCAggABIIBWEc5vdvKj55j5NRs +fFmvBeF/OoTJ2/M1yClq4D3RILOzwb8HQ2EN6YtlOxzQK9r/5FXUi9sgYPi9nrHG +4pQFFpQapZ7TbIYZ1As6WXHDaju1Dt+mO5See11TV8VW6SvAk1V8mWcGnwpaqH2g +ZdGb1tmOxky0kCr31rb0mpWp6kFccvkjX+dve2K4AWS59SeK5MVdjiBFRyQ2+qga +rFp4kCn15orSc2xrkyiAXiacUvs1ixp7poJCv2y7oAcItEJ1pAjV8UHEzau0rgHq +JJhJGD6d5o1Pt4dMtQhdxOvjpPfz6d3qFHIoYNwYAtkVxHowQ8Uv0I3sXjKFwMqU +zqztXXYH23BOq+OrxDNIiqnNtbOOhrv2SE2Tn342OGd001JfgXhyJSPugztN5NvB +L5o4dPYUp1wbBBoG52nzqd44LDy1v5tyzijhlyoo+YzVwfiMHK1rb8sEwO4U +-----END ENCRYPTED PRIVATE KEY----- diff --git a/servers/zts/src/test/resources/test_private.v1 b/servers/zts/src/test/resources/test_private.v1 new file mode 100644 index 00000000000..0044591884d --- /dev/null +++ b/servers/zts/src/test/resources/test_private.v1 @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTqa+RwOH3ZcMKyoGxOJo9A +yeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQJARpQBv09w/j6O7TmgtJm4 +Ws5bIxMgOkrHaqPs2Epq8rX8FUyaYSHBL4yYhqWlno4j1TrLzWneni8kJCUNJydU +AQIhAPXBXPQ3UzzNeQC5gbcweMMIQOYHyF7W/NAEILSKgy6hAiEAxL7hOjpdYGtj +hjG1nVIs+HW6z5TnRig7JjTwqA8RMdECIQC4oPCIuRfb0jJaDQQa8FuJiqXXK3mp +ZrLARJmdiYJMgQIgZxpAnWsIlAay2RgjvJXbyzim9TFrIXDjzlnf47JBqIECIBue +3ht9BnawOQqGzFjD89dFQ6nZtbiAaqORj276/SzZ +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/servers/zts/src/test/resources/test_public.v1 b/servers/zts/src/test/resources/test_public.v1 new file mode 100644 index 00000000000..61accb7bc31 --- /dev/null +++ b/servers/zts/src/test/resources/test_public.v1 @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTq +a+RwOH3ZcMKyoGxOJo9AyeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQ== +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/servers/zts/src/test/resources/valid.csr b/servers/zts/src/test/resources/valid.csr new file mode 100644 index 00000000000..6c6bbd517be --- /dev/null +++ b/servers/zts/src/test/resources/valid.csr @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBGjCBxQIBADBgMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcT +CVN1bm55dmFsZTEYMBYGA1UEChMPTXkgVGVzdCBDb21wYW55MRYwFAYDVQQDEw1h +dGhlbnouc3luY2VyMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKrvfvBgXWqWAorw +5hYJu3dpOJe0gp3nTgiiPGT7+jzm6BRcssOBTPFIMkePT2a8Tq+FYSmFnHfbQjwm +Yw2uMK8CAwEAAaAAMA0GCSqGSIb3DQEBBQUAA0EANa7mSdMKoQc1OZPM6qhpp2Vi +49bRxbXLvRLE9f6rbIDkvjgPenpLw2xWgOdtwYcTy9TK0PCWB6dOSk4fkN+bBQ== +-----END CERTIFICATE REQUEST----- diff --git a/servers/zts/src/test/resources/valid_cn_x509.cert b/servers/zts/src/test/resources/valid_cn_x509.cert new file mode 100644 index 00000000000..805615e10e5 --- /dev/null +++ b/servers/zts/src/test/resources/valid_cn_x509.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICfzCCAimgAwIBAgIJAMIRugkwe7mwMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRgwFgYDVQQK +Ew9NeSBUZXN0IENvbXBhbnkxFjAUBgNVBAMTDWF0aGVuei5zeW5jZXIwHhcNMTYx +MjA5MjE1NDQ2WhcNMTcxMjA5MjE1NDQ2WjBgMQswCQYDVQQGEwJVUzELMAkGA1UE +CBMCQ0ExEjAQBgNVBAcTCVN1bm55dmFsZTEYMBYGA1UEChMPTXkgVGVzdCBDb21w +YW55MRYwFAYDVQQDEw1hdGhlbnouc3luY2VyMFwwDQYJKoZIhvcNAQEBBQADSwAw +SAJBANImoEeD7LU+x6x5dAnNRFtrbab2yxrDXFXdwKQxlIs2PWfLeo3Xq0e2bOU6 +kQDgFz/IkMN3tEyDgQ/Nv0eAbckCAwEAAaOBxTCBwjAdBgNVHQ4EFgQUBi0msMNX +iC2XLpKjXPz1DMf9PoEwgZIGA1UdIwSBijCBh4AUBi0msMNXiC2XLpKjXPz1DMf9 +PoGhZKRiMGAxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vu +bnl2YWxlMRgwFgYDVQQKEw9NeSBUZXN0IENvbXBhbnkxFjAUBgNVBAMTDWF0aGVu +ei5zeW5jZXKCCQDCEboJMHu5sDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A0EAPnVkUdicQzk1809lHNyhSqb6p+kxjC52RfI8bZ+kACOppLLCpNu3nuuh+9yT +J1U2YLjE774n48u74k7EEmvJ3A== +-----END CERTIFICATE----- diff --git a/servers/zts/src/test/resources/zts_private.pem b/servers/zts/src/test/resources/zts_private.pem new file mode 100644 index 00000000000..356e1edf8bd --- /dev/null +++ b/servers/zts/src/test/resources/zts_private.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTqa+RwOH3ZcMKyoGxOJo9A +yeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQJARpQBv09w/j6O7TmgtJm4 +Ws5bIxMgOkrHaqPs2Epq8rX8FUyaYSHBL4yYhqWlno4j1TrLzWneni8kJCUNJydU +AQIhAPXBXPQ3UzzNeQC5gbcweMMIQOYHyF7W/NAEILSKgy6hAiEAxL7hOjpdYGtj +hjG1nVIs+HW6z5TnRig7JjTwqA8RMdECIQC4oPCIuRfb0jJaDQQa8FuJiqXXK3mp +ZrLARJmdiYJMgQIgZxpAnWsIlAay2RgjvJXbyzim9TFrIXDjzlnf47JBqIECIBue +3ht9BnawOQqGzFjD89dFQ6nZtbiAaqORj276/SzZ +-----END RSA PRIVATE KEY----- diff --git a/servers/zts/src/test/resources/zts_public.pem b/servers/zts/src/test/resources/zts_public.pem new file mode 100644 index 00000000000..727c3c67f22 --- /dev/null +++ b/servers/zts/src/test/resources/zts_public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTq +a+RwOH3ZcMKyoGxOJo9AyeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQ== +-----END PUBLIC KEY----- diff --git a/utils/zms_cli/README.md b/utils/zms_cli/README.md new file mode 100644 index 00000000000..3de1d5720b1 --- /dev/null +++ b/utils/zms_cli/README.md @@ -0,0 +1,11 @@ +zms-cli +======= + +ZMS Client application in go to manage your Athenz domain in ZMS Server. + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() + diff --git a/utils/zms_cli/zms-cli.go b/utils/zms_cli/zms-cli.go new file mode 100644 index 00000000000..780a849e285 --- /dev/null +++ b/utils/zms_cli/zms-cli.go @@ -0,0 +1,312 @@ +package main + +import ( + "bytes" + "crypto/tls" + "encoding/base64" + "errors" + "flag" + "fmt" + "golang.org/x/crypto/ssh/terminal" + "golang.org/x/net/proxy" + "io/ioutil" + "log" + "net" + "net/http" + "os" + "strings" + "syscall" + "time" + + "../../clients/go/zms" + "../../libs/go/zmscli" +) + +//these get set by the build script via the LDFLAGS +var VERSION string +var BUILD_DATE string + +func defaultZmsUrl() string { + s := os.Getenv("ZMS") + if s != "" { + return s + } + return "https://localhost:4443/zms/v1" +} + +func defaultIdentity() string { + return "user." + os.Getenv("USER") +} + +func defaultSocksProxy() string { + return os.Getenv("SOCKS5_PROXY") +} + +func defaultDebug() bool { + sDebug := os.Getenv("ZMS_DEBUG") + if sDebug == "true" { + return true + } + return false +} + +func debugAuthNToken(identity string) string { + i := strings.LastIndex(identity, ".") + domain := identity[0:i] + name := identity[i+1:] + var buf bytes.Buffer + buf.WriteString("v=U1;d=" + domain + ";n=" + name + ";s=fakesignature") + return "v=U1;d=" + domain + ";n=" + name + ";s=fakesignature" +} + +func isFreshFile(filename string, maxAge float64) bool { + info, err := os.Stat(filename) + if err != nil { + return false + } + delta := time.Since(info.ModTime()) + duration := delta + if duration.Minutes() > maxAge { + return false + } + return true +} + +func getCachedNToken() string { + ntokenFile := os.Getenv("HOME") + "/.ntoken" + if isFreshFile(ntokenFile, 45) { + data, err := ioutil.ReadFile(ntokenFile) + if err == nil { + return string(data) + } else { + fmt.Printf("Couldn't read the file, error: %v\n", err) + } + } + return "" +} + +func getAuthNToken(identity, authorizedServices, zmsUrl string, tr *http.Transport) (string, error) { + // our identity must be user + if !strings.HasPrefix(identity, "user.") { + return "", errors.New("Identity must start with user.") + } + user := identity[5:] + ntoken := getCachedNToken() + if ntoken != "" { + return ntoken, nil + } + + fmt.Fprintf(os.Stderr, "Enter password for "+user+": ") + pass, err := terminal.ReadPassword(syscall.Stdin) + if err != nil { + return "", err + } + password := string(pass) + // The user types during terminal.ReadPassword() however + // since echo is turned off the cursor doesn't make it to the next line. + fmt.Fprint(os.Stderr, "\n") + + data := []byte(user + ":" + password) + str := base64.StdEncoding.EncodeToString(data) + + var authHeader = "Authorization" + var authCreds = "Basic " + str + zmsClient := zms.ZMSClient{ + URL: zmsUrl, + Transport: tr, + CredsHeader: &authHeader, + CredsToken: &authCreds, + Timeout: 0, + } + tok, err := zmsClient.GetUserToken(zms.SimpleName(user), authorizedServices) + if err != nil { + return "", fmt.Errorf("Cannot get user token for user: %s error: %v", user, err) + } + if tok.Token != "" { + ntokenFile := os.Getenv("HOME") + "/.ntoken" + data := []byte(tok.Token) + ioutil.WriteFile(ntokenFile, data, 0600) + } + return string(tok.Token), nil +} + +func usage() string { + var buf bytes.Buffer + buf.WriteString("usage: zms-cli [flags] command [params]\n") + buf.WriteString(" flags:\n") + buf.WriteString(" -a auditRef Audit Reference Token if auditing is required for domain\n") + buf.WriteString(" -b Bulk import/update mode. Do not read/display updated role/policy/service objects (default=false)\n") + buf.WriteString(" -d domain The domain used for every command that takes a domain argument\n") + buf.WriteString(" -f ntoken_file Principal Authority NToken file used for authentication\n") + buf.WriteString(" -i identity User identity to authenticate as if NToken file is not specified\n") + buf.WriteString(" (default=" + defaultIdentity() + ")\n") + buf.WriteString(" -k Disable peer verification of SSL certificates.\n") + buf.WriteString(" -s host:port The SOCKS5 proxy to route requests through\n") + buf.WriteString(" -v Verbose mode. YRNs are included in output (default=false)\n") + buf.WriteString(" -z zms_url Base URL of the ZMS server to use\n") + buf.WriteString(" (default ZMS=" + defaultZmsUrl() + ")\n") + buf.WriteString(" -debug Debug mode. Generates debug NTokens (default=false)\n") + buf.WriteString("\n") + buf.WriteString(" type 'zms-cli help' to see all available commands\n") + buf.WriteString(" type 'zms-cli help [command]' for usage of the specified command\n") + return buf.String() +} + +func loadNtokenFromFile(fileName string) (string, error) { + buf, err := ioutil.ReadFile(fileName) + if err != nil { + return "", err + } + return string(buf), nil +} + +func main() { + pZMS := flag.String("z", defaultZmsUrl(), "Base URL of the ZMS server to use") + pIdentity := flag.String("i", defaultIdentity(), "the identity to authenticate as") + pNtokenFile := flag.String("f", "", "ntoken file path") + pVerbose := flag.Bool("v", false, "verbose mode. YRNs are included in output") + pBulkmode := flag.Bool("b", false, "bulk mode. Do not display updated role/policy/service in output") + pProductIdSupport := flag.Bool("p", false, "Top Level Domain add operations require product ids") + pDomain := flag.String("d", "", "The domain for the command to execute in. If not specified, only certain commands are available") + pUserDomain := flag.String("u", "user", "User domain name as configured in Athens systems") + pSocks := flag.String("s", defaultSocksProxy(), "The SOCKS5 proxy to route requests through, i.e. 127.0.0.1:1080") + pSkipVerify := flag.Bool("k", false, "Disable peer verification of SSL certificates") + pDebug := flag.Bool("debug", defaultDebug(), "debug mode (for authentication, mainly)") + pAuditRef := flag.String("a", "", "Audit Reference Token if auditing is enabled for the domain") + flag.Usage = func() { + fmt.Println(usage()) + } + + // first we need to parse our arguments based + // on the flags we defined above + + flag.Parse() + + if *pZMS == "" { + fmt.Println("No ZMS Url specified") + return + } + + // before processing our arguments verify that the zms + // url is of expected format and return failure right + // away otherwise the error message is somewhat + // confusing when the hostname/port is valid but the + // uri part is invalid resulting in 404 responses + + if !strings.HasSuffix(*pZMS, "/zms/v1") { + fmt.Println("Invalid ZMS Url specified: " + *pZMS) + fmt.Println("Valid ZMS Url has the following form: https://:/zms/v1") + return + } + + // now process our request + + args := flag.Args() + if len(args) == 0 { + fmt.Println(usage()) + return + } else if args[0] == "help" { + cli := zmscli.Zms{} + cli.UserDomain = *pUserDomain + if len(args) == 2 { + fmt.Println(cli.HelpSpecificCommand(false, args[1])) + } else { + fmt.Println(cli.HelpListCommand()) + } + return + } else if args[0] == "version" { + if VERSION == "" { + fmt.Println("zms-cli (development version)") + } else { + fmt.Println("zms-cli " + VERSION + " " + BUILD_DATE) + } + return + } + + if *pSocks == "" { + pSocks = nil + } + identity := *pIdentity + if strings.Index(identity, ".") < 0 { + identity = "user." + identity + } + tr := getHttpTransport(pSocks, *pSkipVerify) + var ntoken string + var err error + if *pNtokenFile == "" { + if *pDebug { + ntoken = debugAuthNToken(identity) + } else { + ntoken, err = getAuthNToken(identity, "", *pZMS, tr) + if err != nil { + log.Fatalf("Unable to get NToken: %v", err) + } + } + } else { + ntoken, err = loadNtokenFromFile(*pNtokenFile) + if err != nil { + log.Fatalf("Unable to load ntoken from file: '%s' err: %v", *pNtokenFile, err) + } + } + var authHeader = "Athenz-Principal-Auth" + + cli := zmscli.Zms{ + ZmsUrl: *pZMS, + Identity: *pIdentity, + Verbose: *pVerbose, + Bulkmode: *pBulkmode, + Interactive: false, + Domain: *pDomain, + AuditRef: *pAuditRef, + UserDomain: *pUserDomain, + ProductIdSupport: *pProductIdSupport, + Debug: *pDebug, + } + cli.SetClient(tr, &authHeader, &ntoken) + + if len(args) > 0 { + switch args[0] { + case "get-user-token": + if len(args) == 2 { + ntoken, err = getAuthNToken(identity, args[1], *pZMS, tr) + if err != nil { + log.Fatalf("Unable to get NToken for service: %s error: %v", args[1], err) + } + } + fmt.Println("Athenz-Principal-Auth: " + ntoken) + return + case "repl": + cli.Interactive = true + cli.Repl() + return + default: + break + } + } + msg, err := cli.EvalCommand(args) + if err != nil { + fmt.Println("***", err) + os.Exit(1) + } else if msg != nil { + fmt.Println(*msg) + os.Exit(0) + } +} + +func getHttpTransport(socksProxy *string, skipVerify bool) *http.Transport { + tr := http.Transport{} + if socksProxy == nil && skipVerify == false { + return &tr + } + if socksProxy != nil { + dialer := &net.Dialer{} + dialSocksProxy, err := proxy.SOCKS5("tcp", *socksProxy, nil, dialer) + if err == nil { + tr.Dial = dialSocksProxy.Dial + } + } + if skipVerify { + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: skipVerify} + } + return &tr +} diff --git a/utils/zpe_policy_updater/README.md b/utils/zpe_policy_updater/README.md new file mode 100644 index 00000000000..3276756dab5 --- /dev/null +++ b/utils/zpe_policy_updater/README.md @@ -0,0 +1,9 @@ +ZPE Policy Updater Application + +Like ZTS and ZPE, ZPU is only needed to support the decentralized authorization. The policy updater is the utility that retrieves from ZTS the policy files for provisioned domains on a host, which ZPE uses to evaluate access requests.This application must be setup as a cron job to periodically (e.g. every 2 hours) to update the policy files. + +## License + +Copyright 2016 Yahoo Inc. + +Licensed under the Apache License, Version 2.0: [http://www.apache.org/licenses/LICENSE-2.0]() diff --git a/utils/zpe_policy_updater/conf/logback.xml b/utils/zpe_policy_updater/conf/logback.xml new file mode 100755 index 00000000000..2fb966be627 --- /dev/null +++ b/utils/zpe_policy_updater/conf/logback.xml @@ -0,0 +1,29 @@ + + + + + + + + + ${LOG_DIR}/application.log + true + + + ${LOG_DIR}/application.%d.log + 7 + true + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n + + + + + + + + + + diff --git a/utils/zpe_policy_updater/conf/utility_settings b/utils/zpe_policy_updater/conf/utility_settings new file mode 100644 index 00000000000..7246e532d4c --- /dev/null +++ b/utils/zpe_policy_updater/conf/utility_settings @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# ** directory location for zpu to store policy files +UTILITY_POLICY_FILE_DIR="${ROOT}/var/zpe" + +# ** athenz configuration file +UTILITY_ATHENZ_CONF="conf/zpe_policy_updater/athenz.conf" + +# ** logback configuration file +UTILITY_LOG_CONFIG="${ROOT}/conf/zpe_policy_updater/logback.xml" + +# ** truststore path +UTILITY_SSL_TRUSTSTORE="${ROOT}/var/zpe_policy_updater/certs/zpu_truststore.jks" diff --git a/utils/zpe_policy_updater/conf/zpu.conf b/utils/zpe_policy_updater/conf/zpu.conf new file mode 100644 index 00000000000..0d139dcfbc4 --- /dev/null +++ b/utils/zpe_policy_updater/conf/zpu.conf @@ -0,0 +1,3 @@ +{ + "domains": "," +} \ No newline at end of file diff --git a/utils/zpe_policy_updater/pom.xml b/utils/zpe_policy_updater/pom.xml new file mode 100644 index 00000000000..c7f952b685e --- /dev/null +++ b/utils/zpe_policy_updater/pom.xml @@ -0,0 +1,91 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.0-SNAPSHOT + ../../pom.xml + + + zpe_policy_updater + jar + zpe_policy_updater + ZPE Policy Updater + + + + ${project.groupId} + zts_core + ${project.parent.version} + + + ${project.groupId} + auth_core + ${project.parent.version} + + + ${project.groupId} + zts_java_client + ${project.parent.version} + + + ${project.groupId} + client_common + ${project.parent.version} + + + ch.qos.logback + logback-classic + 1.1.3 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + diff --git a/utils/zpe_policy_updater/scripts/zpu_run.sh b/utils/zpe_policy_updater/scripts/zpu_run.sh new file mode 100755 index 00000000000..d6e68b91aea --- /dev/null +++ b/utils/zpe_policy_updater/scripts/zpu_run.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +if [ -z "${ROOT}" ]; then + ROOT="/home/athenz" +fi + +UTILITY_NAME=zpe_policy_updater +UTILITY_CLASSPATH=${ROOT}/lib/jar/zpe_policy_updater*.jar:${ROOT}/lib/jars/* +UTILITY_BOOTSTRAP_CLASS=com.yahoo.athenz.zpe_policy_updater.PolicyUpdater + +## pick up our service settings which should override +# some of the default values set by the code +if [ ! -f ${ROOT}/conf/${UTILITY_NAME}/utility_settings ]; then + echo "Unable to find utility settings: ${ROOT}/conf/${UTILITY_NAME}/utility_settings aborting" + exit -1 +fi + +. "${ROOT}/conf/${UTILITY_NAME}/utility_settings" + +export JAVA_OPTS="${JAVA_OPTS} -Dathenz.zpe_policy_updater.dir=${UTILITY_POLICY_FILE_DIR}" +export JAVA_OPTS="${JAVA_OPTS} -Dlogback.configurationFile=${UTILITY_LOG_CONFIG}" +export JAVA_OPTS="${JAVA_OPTS} -Dathenz.athenz_conf=${UTILITY_ATHENZ_CONF}" +export JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStore=${UTILITY_SSL_TRUSTSTORE}" + +if [ ! -d "${UTILITY_POLICY_FILE_DIR}" ]; then + mkdir -p ${UTILITY_POLICY_FILE_DIR} +fi + +date + +echo "Executing: java -classpath ${UTILITY_CLASSPATH} ${JAVA_OPTS} ${UTILITY_BOOTSTRAP_CLASS}" + +java -classpath ${UTILITY_CLASSPATH} ${JAVA_OPTS} ${UTILITY_BOOTSTRAP_CLASS} 2>&1 < /dev/null diff --git a/utils/zpe_policy_updater/scripts/zpu_set_ownership.pl b/utils/zpe_policy_updater/scripts/zpu_set_ownership.pl new file mode 100755 index 00000000000..3d505146c9c --- /dev/null +++ b/utils/zpe_policy_updater/scripts/zpu_set_ownership.pl @@ -0,0 +1,42 @@ +#!/usr/local/bin/perl -w + +use strict; + +my $ROOT = $ENV{ROOT}; +if (!$ROOT) { + $ROOT = "/home/athenz"; +} + +my $num_args = $#ARGV + 1; +if ($num_args != 1) { + exit 1; +} + +my $zpu_user = $ARGV[0]; + +system("chown -R $zpu_user $ROOT/var/zpe"); +my $ret = $?; +if ($ret != 0) { + print "Unable to chown $zpu_user for file $ROOT/var/zpe\n"; + exit 2; +} +# before running chown on tmp/zpe directory +# make sure it exists. it's possible that +# it was cleaned up which is ok since zpu +# will create it with the ownership of the +# running user when it gets executed +if (-d "$ROOT/tmp/zpe") { + system("chown -R $zpu_user $ROOT/tmp/zpe"); + $ret = $?; + if ($ret != 0) { + print "Unable to chown $zpu_user for file $ROOT/tmp/zpe\n"; + exit 3; + } +} +system("chown -R $zpu_user $ROOT/logs/zpe_policy_updater"); +$ret = $?; +if ($ret != 0) { + print "Unable to chown $zpu_user for file $ROOT/logs/zpe_policy_updater\n"; + exit 4; +} +exit 0; diff --git a/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdater.java b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdater.java new file mode 100644 index 00000000000..e96876da509 --- /dev/null +++ b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdater.java @@ -0,0 +1,423 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.security.PublicKey; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Timestamp; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.utils.SignUtils; +import com.yahoo.athenz.sia.SIA; +import com.yahoo.athenz.zts.DomainMetrics; +import com.yahoo.athenz.zts.DomainSignedPolicyData; +import com.yahoo.athenz.zts.PolicyData; +import com.yahoo.athenz.zts.SignedPolicyData; +import com.yahoo.athenz.zts.ZTSClient; +import com.yahoo.athenz.zts.ZTSClientException; + +public class PolicyUpdater { + + private static final Logger LOG = LoggerFactory.getLogger(PolicyUpdater.class); + + private static final String POLICY_FILE_EXTENSION = ".pol"; + private static final String TEMP_FILE_EXTENSION = ".tmp"; + + public static final String ZPE_METRIC_FILE_PATH = "/var/zpe_stat/"; + public static final String ZPE_PROP_METRIC_FILE_PATH = "athenz.zpe.metric_file_path"; + + static final String METRIC_GENERAL_FAILURE = "zpu_general_fail_sum"; + static final String METRIC_PROCESS_SUM = "zpu_process_sum"; + static final String METRIC_DOMAIN_FAILURE = "domain_fail_sum"; + static final String METRIC_DOMAIN_SUCCESS = "domain_good_sum"; + static final String METRIC_DOMAIN_FAIL = "domain_fail"; + static final String METRIC_DOMAIN_GOOD = "domain_good"; + + private enum ZPUExitCode { + SUCCESS(0), + CONFIG_CREATE_FAILURE(1), + CONFIG_INIT_FAILURE(2), + MAX_INSTANCE_FAILURE(3), + POLICY_UPDATE_FAILURE(4); + + private int code; + ZPUExitCode(int code) { + this.code = code; + } + int getCode() { + return code; + } + }; + + public static void main(String[] args) throws IOException, InterruptedException { + + PolicyUpdaterConfiguration configuration = null; + try { + configuration = new PolicyUpdaterConfiguration(); + } catch (Exception ex) { + LOG.error("Unable to create configuration object: ", ex); + System.exit(ZPUExitCode.CONFIG_CREATE_FAILURE.getCode()); + } + + Random randomGenerator = new Random(); + int randmonSleepInterval = 0; + + if (configuration.getStartupDelayIntervalInSecs() > 0) { + randmonSleepInterval = randomGenerator.nextInt(configuration.getStartupDelayIntervalInSecs()); + LOG.info("Launching zpe_policy_updater in " + randmonSleepInterval + " seconds..."); + for (int i = 0; i < randmonSleepInterval; i++) { + Thread.sleep(1000); + } + } else { + LOG.info("Launching zpe_policy_updater with no delay..."); + } + + ZPUExitCode exitCode = ZPUExitCode.SUCCESS; + try { + try { + configuration.init(null, null); + } catch (Exception ex) { + LOG.error("Unable to initialize configuration object: ", ex); + exitCode = ZPUExitCode.CONFIG_INIT_FAILURE; + throw ex; + } + + try { + PolicyUpdater.policyUpdater(configuration, new SIAClientFactoryImpl(), new ZTSClientFactoryImpl()); + } catch (Exception ex) { + LOG.error("PolicyUpdater: Unable to update policy data: ", ex); + exitCode = ZPUExitCode.POLICY_UPDATE_FAILURE; + throw ex; + } + } catch (Exception exc) { + LOG.error("PolicyUpdater: Exiting upon error: " + exc.getMessage()); + } finally { + System.exit(exitCode.getCode()); + } + } + + static void policyUpdater(PolicyUpdaterConfiguration configuration, SIAClientFactory siaFactory, + ZTSClientFactory ztsFactory) throws Exception { + + try (ZTSClient zts = ztsFactory.create()) { + + List domainList = configuration.getDomainList(); + if (domainList == null) { + SIA siaClient = siaFactory.create(); + domainList = siaClient.getDomainList(); + LOG.info("policyUpdater: Number of domains returned from SIA:" + (domainList == null ? 0 : domainList.size())); + } + + LOG.info("policyUpdater: Number of domains to process:" + (domainList == null ? 0 : domainList.size())); + if (domainList == null) { + LOG.error("policyUpdater: no domain list to process from configuration or SIA"); + throw new Exception("no configured domains to process"); + } + + for (String domain : domainList) { + + LOG.info("Fetching signed policies for domain:" + domain); + + String matchingTag = getEtagForExistingPolicy(zts, configuration, domain); + + Map> responseHeaders = null; + DomainSignedPolicyData domainSignedPolicyData = null; + try { + domainSignedPolicyData = zts.getDomainSignedPolicyData(domain, matchingTag, responseHeaders); + } catch (Exception exc) { + domainSignedPolicyData = null; + LOG.error("PolicyUpdater: Unable to retrieve policies from zts for domain=" + domain, exc); + } + if (domainSignedPolicyData == null) { + if (matchingTag != null && !matchingTag.isEmpty()) { + LOG.info("PolicyUpdater: Policies not updated since last fetch time"); + } + } else if (validateSignedPolicies(zts, configuration, domainSignedPolicyData, domain)) { + writePolicies(configuration, domain, domainSignedPolicyData); + } + } + + // now push the domain metrics files + + postDomainMetrics(zts); + } + } + + static boolean validateSignedPolicies(ZTSClient zts, PolicyUpdaterConfiguration configuration, + DomainSignedPolicyData domainSignedPolicyData, String domain) { + + if (domainSignedPolicyData == null || domain == null) { + throw new IllegalArgumentException("null parameters are not valid arguments"); + } + + LOG.info("Checking expiration time for:" + domain); + + Timestamp expires = domainSignedPolicyData.getSignedPolicyData().getExpires(); + if (System.currentTimeMillis() > expires.millis()) { + LOG.error("Signed policy for domain:" + domain + " was expired."); + return false; + } + + // first we're going to verify the ZTS signature for the data + + LOG.info("Verifying ZTS signature for: " + domain); + SignedPolicyData signedPolicyData = domainSignedPolicyData.getSignedPolicyData(); + + LOG.debug("Policies retrieved from the ZTS server: " + signedPolicyData); + + String signature = domainSignedPolicyData.getSignature(); + String keyId = domainSignedPolicyData.getKeyId(); + LOG.debug("validateSignedPolicies: domain=" + domain + " zts key id=" + keyId + " Digital ZTS signature=" + signature); + + PublicKey ztsPublicKey = configuration.getZtsPublicKey(zts, keyId); + if (ztsPublicKey == null) { + LOG.error("validateSignedPolicies: Missing ZTS Public key for id: " + keyId); + return false; + } + + boolean verified = Crypto.verify(SignUtils.asCanonicalString(signedPolicyData), ztsPublicKey, signature); + if (verified == false) { + LOG.error("Signed policy for domain:" + domain + " failed ZTS signature verification."); + LOG.error("ZTS Signature: " + signature + ". Policies data returned from ZTS: " + signedPolicyData); + return false; + } + + // then we're going to verify the ZMS signature for the policy data + + LOG.info("Verifying ZMS signature for: " + domain); + PolicyData policyData = signedPolicyData.getPolicyData(); + + signature = signedPolicyData.getZmsSignature(); + LOG.debug("Digital ZMS signature: " + signature); + keyId = signedPolicyData.getZmsKeyId(); + LOG.debug("Digital ZMS signature key Id: " + keyId); + + PublicKey zmsPublicKey = configuration.getZmsPublicKey(zts, keyId); + if (zmsPublicKey == null) { + LOG.error("Missing ZMS Public key with id: " + keyId); + return false; + } + + verified = Crypto.verify(SignUtils.asCanonicalString(policyData), zmsPublicKey, signature); + if (verified == false) { + LOG.error("Signed policy for domain:" + domain + " failed ZMS signature verification."); + LOG.error("ZMS Signature: " + signature + ". Policies data returned from ZTS: " + policyData); + } + + return verified; + } + + static void verifyTmpDirSetup(PolicyUpdaterConfiguration configuration) throws IOException { + // ensure tmp dir exists + String policyTmpDir = configuration.getPolicyFileTmpDir(); + Path tmpDir = Paths.get(policyTmpDir); + if (java.nio.file.Files.exists(tmpDir)) { + return; + } + + LOG.warn("The temp dir doesnt exist so will create it: " + tmpDir); + java.nio.file.Files.createDirectory(tmpDir); + + // get the user from config file to perform chown aginst the tmp dir + // chown -R $zpu_user $ROOT/tmp/zpe + String user = configuration.getZpuDirOwner(); + if (user == null) { + LOG.warn("Cannot chown of the temp dir: " + tmpDir + " : no configured user"); + return; + } + + try { + java.nio.file.attribute.UserPrincipalLookupService lookupSvc = + java.nio.file.FileSystems.getDefault().getUserPrincipalLookupService(); + java.nio.file.attribute.UserPrincipal uprinc = lookupSvc.lookupPrincipalByName(user); + Files.setOwner(tmpDir, uprinc); + } catch (Exception exc) { + LOG.warn("Failed to chown of the temp dir: " + tmpDir + " : user: " + user + " : exc: " + exc); + } + } + + static void writePolicies(PolicyUpdaterConfiguration configuration, String domain, + DomainSignedPolicyData domainSignedPolicyData) throws IOException { + + if (configuration == null) { + throw new IllegalArgumentException("null configuration"); + } + String policyTmpDir = configuration.getPolicyFileTmpDir(); + String policyDir = configuration.getPolicyFileDir(); + if (policyTmpDir == null || policyDir == null || domain == null || domainSignedPolicyData == null) { + throw new IllegalArgumentException("null parameters are not valid arguments"); + } + + String pathToTempFile = policyTmpDir + File.separator + domain + TEMP_FILE_EXTENSION; + String pathToPolicyFile = policyDir + File.separator + domain + POLICY_FILE_EXTENSION; + + // ensure tmp dir exists + verifyTmpDirSetup(configuration); + + LOG.info("Writing temp policy file: " + pathToTempFile); + // Make a file object from the path name + File file = new File(pathToTempFile); + file.createNewFile(); + Files.write(file.toPath(), JSON.bytes(domainSignedPolicyData)); + + Path sourceFile = Paths.get(pathToTempFile); + Path destinationFile = Paths.get(pathToPolicyFile); + try { + LOG.info("Moving temp file : " + sourceFile + " to destination: " + destinationFile); + Files.copy(sourceFile, destinationFile, StandardCopyOption.REPLACE_EXISTING); + Files.deleteIfExists(sourceFile); + } catch (IOException exc) { + LOG.error("PolicyUpdater: Moving temp file failure. source: " + sourceFile + " : destination: " + destinationFile + " : exc: " + exc); + exc.printStackTrace(); + } + } + + static String getEtagForExistingPolicy(ZTSClient zts, PolicyUpdaterConfiguration configuration, + String domain) { + + if (domain == null) { + throw new IllegalArgumentException("getEtagForExistingPolicy: null parameters are not valid arguments"); + } + + String policyDir = configuration.getPolicyFileDir(); + if (policyDir == null) { + throw new IllegalArgumentException("getEtagForExistingPolicy: Invalid configuration: no policy directory path"); + } + + String policyDirPath; + if (policyDir.length() - 1 != policyDir.lastIndexOf(File.separator)) { + policyDirPath = policyDir + File.separator; + } else { + policyDirPath = policyDir; + } + + String etag = null; + String policyFile = policyDirPath + domain + POLICY_FILE_EXTENSION; + + LOG.info("Decoding " + policyFile + " to retrieve eTag from policy file."); + File file = new File(policyFile); + + if (file.exists() == false) { + LOG.info("Policy file not found."); + return etag; + } + + DomainSignedPolicyData domainSignedPolicyData = null; + try { + domainSignedPolicyData = JSON.fromBytes(Files.readAllBytes(file.toPath()), + DomainSignedPolicyData.class); + } catch (Exception ex) { + LOG.info("Unable to parse domain signed policy file: " + policyFile); + return etag; + } + + // validate the signature before checking for expiration + + if (validateSignedPolicies(zts, configuration, domainSignedPolicyData, domain) == false) { + LOG.info("Unable to validate domain signed policy file: " + policyFile); + return etag; + } + + // Check expiration of policies and if its less than the configured interval defined by user + // to get updated policy then return null so that the policies are updated + + LOG.info("Checking expiration time for: " + domain); + long now = System.currentTimeMillis() / 1000; + + Timestamp expires = domainSignedPolicyData.getSignedPolicyData().getExpires(); + + long startupDelayInterval = configuration.getStartupDelayIntervalInSecs(); + + LOG.info("Expiration time for " + domain + " is: " + (expires.millis() / 1000)); + LOG.info("Startup delay: " + startupDelayInterval); + LOG.info("Current time: " + now); + + if (((expires.millis() / 1000) - now) < (startupDelayInterval)) { + LOG.info("Signed policies for domain:" + domain + " are expired, returning null."); + return null; + } + + if (domainSignedPolicyData.getSignedPolicyData().getModified() != null) { + + // ETags are quoted-strings based on the HTTP RFC + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11 + // so we're going to quote our modified timestamp + + etag = "\"" + domainSignedPolicyData.getSignedPolicyData().getModified().toString() + "\""; + LOG.info("ETag: " + etag); + } else { + LOG.info("No ETag found."); + } + + return etag; + } + + static String getFilePath() { + String rootDir = System.getenv("ROOT"); + if (rootDir == null) { + rootDir = "/home/athenz"; + } + final String defaultPath = rootDir + ZPE_METRIC_FILE_PATH; + String filePath = System.getProperty(ZPE_PROP_METRIC_FILE_PATH, defaultPath); + + // verify it ends with the separator and handle accordingly + + if (!filePath.endsWith(File.separator)) { + filePath = filePath.concat(File.separator); + } + return filePath; + } + + public static void postDomainMetrics(ZTSClient zts) { + + final String filepath = getFilePath(); + File dir = new File(filepath); + File[] filenames = dir.listFiles(); + + // make sure we have valid list of metric files + + if (filenames == null) { + return; + } + + for (int i = 0; i < filenames.length; i++) { + String domainName = filenames[i].getName().split("_")[0]; + DomainMetrics domainMetrics = null; + final String metricFile = filepath + filenames[i].getName(); + try { + Path path = Paths.get(metricFile); + domainMetrics = JSON.fromBytes(Files.readAllBytes(path), DomainMetrics.class); + zts.postDomainMetrics(domainName, domainMetrics); + Files.deleteIfExists(path); + } catch (ZTSClientException | IOException ex) { + LOG.error("Unable to push domain metrics from {} - error: {}", + metricFile, ex.getMessage()); + } + } + } +} diff --git a/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdaterConfiguration.java b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdaterConfiguration.java new file mode 100644 index 00000000000..9cc022b7b02 --- /dev/null +++ b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdaterConfiguration.java @@ -0,0 +1,277 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.security.PublicKey; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Struct; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.config.AthenzConfig; +import com.yahoo.athenz.zms.PublicKeyEntry; +import com.yahoo.athenz.zts.ZTSClient; + +public class PolicyUpdaterConfiguration { + + private static final Logger LOG = LoggerFactory.getLogger(PolicyUpdaterConfiguration.class); + + private static final String ZPU_PROP_ATHENZ_CONF = "athenz.athenz_conf"; + private static final String ZPU_PROP_DEBUG = "athenz.zpe_policy_updater.debug"; + private static final String ZPU_PROP_POLDIR = "athenz.zpe_policy_updater.dir"; + private static final String ZPU_PROP_POL_TMP_DIR = "athenz.zpe_policy_updater.tmp_dir"; + static final String ZPU_PROP_TEST_ROOT_PATH = "athenz.zpe_policy_updater.test_root_path"; + static final String ZPU_PROP_METRIC_FACTORY = "athenz.zpe_policy_updater.metric_factory_class"; + + static final String ZPU_METRIC_FACT_DEF_CLASS = "com.yahoo.athenz.common.metrics.impl.NoOpMetricFactory"; + + private static final String ATHENZ_CONFIG_FILE = "/conf/athenz/athenz.conf"; + private static final String ZPU_CONFIG_FILE = "/conf/zpe_policy_updater/zpu.conf"; + private static final String ZPU_CONFIG_DOMAINS = "domains"; + private static final String ZPU_CONFIG_USER = "user"; + private static final String ZPU_USER_DEFAULT = "root"; + + private static final String STARTUP_DELAY = "STARTUP_DELAY"; + private static final int DEFAULT_STARTUP_DELAY = 0; + private static final int MAX_STARTUP_DELAY = 86400; + + public static final String ZTS_PUBLIC_KEY_PREFIX = "zts.public_key."; + public static final String ZMS_PUBLIC_KEY_PREFIX = "zms.public_key."; + + private boolean debugMode = false; + private int startupDelay = 0; + private Map ztsPublicKeyMap = new HashMap(); + private Map zmsPublicKeyMap = new HashMap(); + private String rootDir; + private String policyFileDir; + private String policyFileTmpDir; + private String defaultAthenzConfigFile; + private String defaultZPUConfigFile; + private List domainList = null; + private String zpuDirOwner = null; + + public PolicyUpdaterConfiguration() { + + debugMode = Boolean.parseBoolean(System.getProperty(ZPU_PROP_DEBUG, "false")); + + rootDir = System.getenv("ROOT"); + if (null == rootDir) { + rootDir = File.separator + "home" + File.separator + "athenz"; + } + rootDir = System.getProperty(ZPU_PROP_TEST_ROOT_PATH, rootDir); + + // default configuration file paths + + defaultAthenzConfigFile = System.getProperty(ZPU_PROP_ATHENZ_CONF, + rootDir + ATHENZ_CONFIG_FILE); + defaultZPUConfigFile = rootDir + ZPU_CONFIG_FILE; + + // Final destination of signed policy files + String policyFileDirDefault = rootDir + File.separator + "var" + + File.separator + "zpe"; + + policyFileDir = System.getProperty(ZPU_PROP_POLDIR, policyFileDirDefault); + + // Temporary destination of signed policy files + String policyFileTmpDirDefault = rootDir + File.separator + "tmp" + + File.separator + "zpe"; + + policyFileTmpDir = System.getProperty(ZPU_PROP_POL_TMP_DIR, policyFileTmpDirDefault); + + String startupDelayString = System.getenv(STARTUP_DELAY); + + if (startupDelayString != null) { + startupDelay = Integer.parseInt(startupDelayString); + } else { + startupDelay = DEFAULT_STARTUP_DELAY; + } + + startupDelay *= 60; // convert from min to secs + + if (startupDelay < 0) { + startupDelay = DEFAULT_STARTUP_DELAY; + } + + if (startupDelay > MAX_STARTUP_DELAY) { + startupDelay = MAX_STARTUP_DELAY; + } + + LOG.info("debug mode: " + debugMode); + LOG.info("policyFileDir: " + policyFileDir); + LOG.info("startup delay: " + startupDelay + " seconds"); + } + + public void init(String pathToAthenzConfigFile, String pathToZPUConfigFile) throws Exception { + + AthenzConfig athenzConfFile = null; + if (pathToAthenzConfigFile == null) { + athenzConfFile = readAthenzConfiguration(defaultAthenzConfigFile); + } else { + athenzConfFile = readAthenzConfiguration(pathToAthenzConfigFile); + } + + LOG.info("Policy Updater configuration is set to:"); + LOG.info("policyFileDir: " + policyFileDir); + + List publicKeys = athenzConfFile.getZtsPublicKeys(); + if (publicKeys != null) { + for (PublicKeyEntry publicKey : publicKeys) { + String keyId = publicKey.getId(); + String key = publicKey.getKey(); + if (key == null || keyId == null) { + continue; + } + addZtsPublicKey(keyId, Crypto.loadPublicKey(Crypto.ybase64DecodeString(key))); + LOG.info("Loaded ztsPublicKey keyId: " + keyId + " key: " + key); + } + } + + publicKeys = athenzConfFile.getZmsPublicKeys(); + if (publicKeys != null) { + for (PublicKeyEntry publicKey : publicKeys) { + String keyId = publicKey.getId(); + String key = publicKey.getKey(); + if (key == null || keyId == null) { + continue; + } + addZmsPublicKey(keyId, Crypto.loadPublicKey(Crypto.ybase64DecodeString(key))); + LOG.info("Loaded zmsPublicKey keyId: " + keyId + " key: " + key); + } + } + + Struct zpuConfFile = null; + if (pathToZPUConfigFile == null) { + zpuConfFile = readZpuConfiguration(defaultZPUConfigFile); + } else { + zpuConfFile = readZpuConfiguration(pathToZPUConfigFile); + } + String domains = zpuConfFile.getString(ZPU_CONFIG_DOMAINS); + if (domains != null && !domains.isEmpty()) { + domainList = Arrays.asList(domains.split(",")); + } + zpuDirOwner = zpuConfFile.getString(ZPU_CONFIG_USER); + if (zpuDirOwner == null || zpuDirOwner.isEmpty()) { + zpuDirOwner = ZPU_USER_DEFAULT; + } + if (isDebugMode()) { + LOG.debug("config-init: user: " + zpuDirOwner + " file=" + pathToZPUConfigFile); + } + } + + private AthenzConfig readAthenzConfiguration(String pathToFile) throws IOException { + LOG.info("Reading configuration file: " + pathToFile); + AthenzConfig conf = null; + try { + Path path = Paths.get(pathToFile); + conf = JSON.fromBytes(Files.readAllBytes(path), AthenzConfig.class); + } catch (Exception e) { + e.printStackTrace(); + } + + return conf; + } + + private Struct readZpuConfiguration(String pathToFile) throws IOException { + LOG.info("Reading configuration file: " + pathToFile); + Struct conf = null; + try { + Path path = Paths.get(pathToFile); + conf = JSON.fromBytes(Files.readAllBytes(path), Struct.class); + } catch (Exception e) { + e.printStackTrace(); + } + + return conf; + } + + public PublicKey getZtsPublicKey(ZTSClient zts, String keyId) { + PublicKey pKey = ztsPublicKeyMap.get(keyId); + + return pKey; + } + + public PublicKey getZmsPublicKey(ZTSClient zts, String keyId) { + PublicKey pKey = zmsPublicKeyMap.get(keyId); + return pKey; + } + + public void addZtsPublicKey(String keyId, PublicKey ztsPublicKey) { + ztsPublicKeyMap.put(keyId, ztsPublicKey); + } + + public void addZmsPublicKey(String keyId, PublicKey zmsPublicKey) { + zmsPublicKeyMap.put(keyId, zmsPublicKey); + } + + public String getRootDir() { + return rootDir; + } + + public void setRootDir(String rootDir) { + this.rootDir = rootDir; + } + + public String getPolicyFileDir() { + return policyFileDir; + } + + public void setPolicyFileDir(String policyFileDir) { + this.policyFileDir = policyFileDir; + } + + public String getPolicyFileTmpDir() { + return policyFileTmpDir; + } + + public void setPolicyFileTmpDir(String policyFileTmpDir) { + this.policyFileTmpDir = policyFileTmpDir; + } + + public boolean isDebugMode() { + return debugMode; + } + + public void setDebugMode(boolean debugMode) { + this.debugMode = debugMode; + } + + public int getStartupDelayIntervalInSecs() { + return startupDelay; + } + + public void setStartupDelayInterval(int startupDelayIntervalInMin) { + this.startupDelay = startupDelayIntervalInMin * 60; + } + + public List getDomainList() { + return domainList; + } + + public String getZpuDirOwner() { + return zpuDirOwner; + } +} diff --git a/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/SIAClientFactory.java b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/SIAClientFactory.java new file mode 100644 index 00000000000..813eb494146 --- /dev/null +++ b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/SIAClientFactory.java @@ -0,0 +1,26 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import java.io.IOException; + +import com.yahoo.athenz.sia.SIA; + +public interface SIAClientFactory { + + public SIA create() throws IOException; + +} diff --git a/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/SIAClientFactoryImpl.java b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/SIAClientFactoryImpl.java new file mode 100644 index 00000000000..4c5604c596d --- /dev/null +++ b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/SIAClientFactoryImpl.java @@ -0,0 +1,28 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import com.yahoo.athenz.sia.SIA; +import com.yahoo.athenz.sia.impl.SIAClient; + +public class SIAClientFactoryImpl implements SIAClientFactory { + + @Override + public SIA create() { + return new SIAClient(); + } + +} diff --git a/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/ZTSClientFactory.java b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/ZTSClientFactory.java new file mode 100644 index 00000000000..2677862e79f --- /dev/null +++ b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/ZTSClientFactory.java @@ -0,0 +1,25 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import java.io.IOException; + +import com.yahoo.athenz.zts.ZTSClient; + +public interface ZTSClientFactory { + public ZTSClient create() throws IOException; + +} diff --git a/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/ZTSClientFactoryImpl.java b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/ZTSClientFactoryImpl.java new file mode 100644 index 00000000000..ca92d247c30 --- /dev/null +++ b/utils/zpe_policy_updater/src/main/java/com/yahoo/athenz/zpe_policy_updater/ZTSClientFactoryImpl.java @@ -0,0 +1,26 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import com.yahoo.athenz.zts.ZTSClient; + +public class ZTSClientFactoryImpl implements ZTSClientFactory { + + @Override + public ZTSClient create() { + return new ZTSClient(null); + } +} diff --git a/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/DebugSIAClientFactory.java b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/DebugSIAClientFactory.java new file mode 100644 index 00000000000..a5d29eae4ff --- /dev/null +++ b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/DebugSIAClientFactory.java @@ -0,0 +1,39 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import java.io.IOException; + +import com.yahoo.athenz.sia.SIA; +import com.yahoo.athenz.zpe_policy_updater.SIAClientFactory; + +public class DebugSIAClientFactory implements SIAClientFactory { + + private boolean emptyDomainList; + + public DebugSIAClientFactory() { + emptyDomainList = false; + } + + public DebugSIAClientFactory(boolean emptyList) { + emptyDomainList = emptyList; + } + @Override + public SIA create() throws IOException { + return new SIAClientMock(emptyDomainList); + } + +} diff --git a/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/DebugZTSClientFactory.java b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/DebugZTSClientFactory.java new file mode 100644 index 00000000000..48f8b5526a6 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/DebugZTSClientFactory.java @@ -0,0 +1,39 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import java.io.IOException; + +import com.yahoo.athenz.zpe_policy_updater.ZTSClientFactory; +import com.yahoo.athenz.zts.ZTSClient; + +public class DebugZTSClientFactory implements ZTSClientFactory { + + private String keyId = "0"; + public void setPublicKeyId(String keyId) { + this.keyId = keyId; + } + + @Override + public ZTSClient create() throws IOException { + ZTSMock zts = new ZTSMock(); + zts.setPublicKeyId(keyId); + ZTSClient client = new ZTSClient("http://localhost:10080", null, null); + client.setZTSRDLGeneratedClient(zts); + client.setSIAClient(new DebugSIAClientFactory().create()); + return client; + } +} diff --git a/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/MockMetricFactory.java b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/MockMetricFactory.java new file mode 100644 index 00000000000..8587a7177d8 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/MockMetricFactory.java @@ -0,0 +1,94 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import java.util.Map; + +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.metrics.MetricFactory; + +import java.util.HashMap; + +public class MockMetricFactory implements MetricFactory { + + @Override + public Metric create() { + return new MockMetric(); + } + + public static class MockMetric implements Metric { + + Map metricMap = new HashMap<>(); + + public void increment(String metric, int count) { + Integer mcnt = metricMap.get(metric); + if (mcnt == null) { + metricMap.put(metric, new Integer(count)); + System.out.println("MockMetric:increment: " + metric + "=" + count); + } else { + int cnt = mcnt.intValue() + count; + metricMap.put(metric, new Integer(cnt)); + System.out.println("MockMetric:increment: " + metric + "=" + cnt); + } + } + + @Override + public void increment(String metric) { + increment(metric, 1); + } + + @Override + public void increment(String metric, String domainName, int count) { + increment(metric + domainName, count); + } + + @Override + public void increment(String metric, String domainName) { + increment(metric + domainName, 1); + } + + public int metricCount(String metric) { + Integer icnt = metricMap.get(metric); + if (icnt == null) { + return -1; + } + return icnt.intValue(); + } + public int metricCount(String metric, String domainName) { + return metricCount(metric + domainName); + } + + @Override + public Object startTiming(String metric, String domainName) { + return null; + } + + @Override + public void stopTiming(Object timerMetric) { + } + + @Override + public void flush() { + metricMap.clear(); + } + + @Override + public void quit() { + metricMap.clear(); + } + } +} + diff --git a/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdaterTest.java b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdaterTest.java new file mode 100644 index 00000000000..af444658c2e --- /dev/null +++ b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/PolicyUpdaterTest.java @@ -0,0 +1,524 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PublicKey; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.sia.SIA; +import com.yahoo.athenz.sia.impl.SIAClient; +import com.yahoo.athenz.zts.DomainSignedPolicyData; +import com.yahoo.athenz.zts.ZTSClient; +import com.yahoo.rdl.JSON; + +import org.testng.Assert; +import org.testng.annotations.Test; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.AfterClass; +import org.mockito.Mockito; + +public class PolicyUpdaterTest { + + private final String pathToAthenzConfigFile = "./src/test/resources/athenz.conf"; + private final String pathToZPUConfigFile = "./src/test/resources/zpu.conf"; + private final String pathToZPUEmptyConfigFile = "./src/test/resources/zpu_empty.conf"; + private final String EXPECTED_ROOT_DIR = "/home/athenz"; + private final String TEST_ROOT_DIR = "/home/myroot"; + + private final String EXPECTED_ZTS_PUBLIC_KEY_K0 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + private final String EXPECTED_ZTS_PUBLIC_KEY_K1 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBUFpyWkMxelBhNXBQZloxaXFtcjdnWW9YaHVIbGlSUApVbnlLelliWWhRZXpUSlJlSDBsdWhvVVdQdTZxeWRHSm54RVUyTldNQ1hZLzhuL1VGSUZvakYwQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-"; + + private final String EXPECTED_POLICY_FILE_TMP_DIR_SUFFIX = "/tmp/zpe"; + private final String TEST_POLICY_DIR = "/pol_dir"; + private final String TEST_POLICY_TEMP_DIR = "/tmp"; + + private final int TEST_STARTUP_DELAY = 1339; + + private PolicyUpdaterConfiguration pupConfig = null; + + @BeforeClass + public void beforeClass() throws Exception { + System.setProperty("athenz.zpe_policy_updater.dir", "src/test/resources"); + System.setProperty("logback.configurationFile", "src/test/resources/logback.xml"); + System.setProperty("athenz.zpe_policy_updater.test_root_path", "src/test/resources"); + System.setProperty("athenz.zpe_policy_updater.metric_factory_class", "com.yahoo.athenz.common.metrics.impl.NoOpMetricFactory"); + + pupConfig = new PolicyUpdaterConfiguration(); + pupConfig.init(pathToAthenzConfigFile, pathToZPUConfigFile); + pupConfig.setPolicyFileTmpDir(pupConfig.getRootDir() + TEST_POLICY_TEMP_DIR); + pupConfig.setPolicyFileDir(pupConfig.getRootDir() + TEST_POLICY_DIR); + } + + @AfterClass + public void afterClass() { + } + + // main has exit, so temporary exclude.. + @Test(enabled=false, expectedExceptions = {Exception.class}) + public void TestMainConfigInitializeFail() throws IOException, InterruptedException { + PolicyUpdater.main(null); + Assert.fail(); + } + + @Test + public void TestPolicyUpdaterConfiguration() throws Exception { + + String rootPath = System.clearProperty(PolicyUpdaterConfiguration.ZPU_PROP_TEST_ROOT_PATH); + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUConfigFile); + Assert.assertEquals(configuration.getRootDir(), EXPECTED_ROOT_DIR); + Assert.assertEquals(configuration.getPolicyFileDir(), "src/test/resources"); //set in pom.xml + Assert.assertEquals(configuration.getPolicyFileTmpDir(), EXPECTED_ROOT_DIR + EXPECTED_POLICY_FILE_TMP_DIR_SUFFIX); + Assert.assertEquals(configuration.getZtsPublicKey(null, "0"), Crypto.loadPublicKey(Crypto.ybase64DecodeString(EXPECTED_ZTS_PUBLIC_KEY_K0))); + Assert.assertEquals(configuration.getZtsPublicKey(null, "1"), Crypto.loadPublicKey(Crypto.ybase64DecodeString(EXPECTED_ZTS_PUBLIC_KEY_K1))); + + List domainList = configuration.getDomainList(); + Assert.assertNotNull(domainList); + Assert.assertEquals(domainList.size(), 2); + Assert.assertTrue(domainList.contains("athenz.ci")); + Assert.assertTrue(domainList.contains("coretech.hosted")); + + // Set the ROOT env variable to /home/myroot + Map newenv = new HashMap(); + newenv.put("ROOT", TEST_ROOT_DIR); + setEnvironmentVar(newenv); + configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUConfigFile); + Assert.assertEquals(configuration.getRootDir(), TEST_ROOT_DIR); + Assert.assertEquals(configuration.getPolicyFileDir(), "src/test/resources"); + Assert.assertEquals(configuration.getPolicyFileTmpDir(), TEST_ROOT_DIR + EXPECTED_POLICY_FILE_TMP_DIR_SUFFIX); + + // use the test root + // + System.setProperty(PolicyUpdaterConfiguration.ZPU_PROP_TEST_ROOT_PATH, rootPath); + + configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUConfigFile); + Assert.assertEquals(configuration.getRootDir(), "src/test/resources"); + Assert.assertEquals(configuration.getPolicyFileDir(), "src/test/resources"); //set in pom.xml + Assert.assertEquals(configuration.getPolicyFileTmpDir(), "src/test/resources" + EXPECTED_POLICY_FILE_TMP_DIR_SUFFIX); + Assert.assertEquals(configuration.getZpuDirOwner(), "root"); + } + + @Test + public void TestVerifySignature() throws Exception { + + System.setProperty(PolicyUpdaterConfiguration.ZPU_PROP_METRIC_FACTORY, "com.yahoo.athenz.zpe_policy_updater.MockMetricFactory"); + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUConfigFile); + + SignPoliciesUtility.signPolicies("./src/test/resources/zts_private_k0.pem", + "./src/test/resources/zms_private_k0.pem", "./src/test/resources/sys.auth.pol", + "./src/test/resources/sys.auth.new.pol"); + + Path path = Paths.get("./src/test/resources/sys.auth.new.pol"); + DomainSignedPolicyData domainPolicySignedData = JSON.fromBytes(Files.readAllBytes(path), DomainSignedPolicyData.class); + Assert.assertTrue(PolicyUpdater.validateSignedPolicies(null, configuration, domainPolicySignedData, "sys.auth.new")); + + // negative test with tampered publickey - zts pubkey failure + PolicyUpdaterConfiguration confMock = Mockito.mock(PolicyUpdaterConfiguration.class); + Mockito.when(confMock.getZtsPublicKey(Mockito.any(ZTSClient.class), Mockito.anyString())).thenReturn(null); + Assert.assertFalse(PolicyUpdater.validateSignedPolicies(null, confMock, domainPolicySignedData, "sys.auth.new")); + + // negative test with tampered publickey - zms pubkey failure + confMock = Mockito.mock(PolicyUpdaterConfiguration.class); + PublicKey pKey = Crypto.loadPublicKey(Crypto.ybase64DecodeString("LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0" + + "RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TD" + + "Nza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbX" + + "ZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIF" + + "BVQkxJQyBLRVktLS0tLQo-")); + Mockito.when(confMock.getZtsPublicKey(Mockito.any(ZTSClient.class), Mockito.anyString())).thenReturn(pKey); + Mockito.when(confMock.getZmsPublicKey(Mockito.any(ZTSClient.class), Mockito.anyString())).thenReturn(null); + Assert.assertFalse(PolicyUpdater.validateSignedPolicies(null, confMock, domainPolicySignedData, "sys.auth.new")); + + // negative test with tampered expiration - zts signature failure + path = Paths.get("./src/test/resources/sys.auth.pol.tampered.zts"); + domainPolicySignedData = JSON.fromBytes(Files.readAllBytes(path), DomainSignedPolicyData.class); + Assert.assertFalse(PolicyUpdater.validateSignedPolicies(null, configuration, domainPolicySignedData, "sys.auth.new")); + + // negative test with tampered actions - zms signature failure + path = Paths.get("./src/test/resources/sys.auth.pol.tampered.zms"); + domainPolicySignedData = JSON.fromBytes(Files.readAllBytes(path), DomainSignedPolicyData.class); + Assert.assertFalse(PolicyUpdater.validateSignedPolicies(null, configuration, domainPolicySignedData, "sys.auth.new")); + + // Test error handling for illegal arguments + boolean exceptionCaught = false; + try { + PolicyUpdater.validateSignedPolicies(null, configuration, null, "sys.auth.new"); + } catch (IllegalArgumentException ex) { + exceptionCaught = true; + } + Assert.assertTrue(exceptionCaught); + + exceptionCaught = false; + try { + PolicyUpdater.validateSignedPolicies(null, configuration, domainPolicySignedData, null); + } catch (IllegalArgumentException ex) { + exceptionCaught = true; + } + Assert.assertTrue(exceptionCaught); + + System.clearProperty(PolicyUpdaterConfiguration.ZPU_PROP_METRIC_FACTORY); + } + + @Test + public void TestValidateExpiredPolicies() throws Exception { + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUConfigFile); + + ZTSMock zts = new ZTSMock(); + zts.setPublicKeyId("0"); + DomainSignedPolicyData domainPolicySignedData = zts.getDomainSignedPolicyData("expiredDomain", null, null); + Assert.assertFalse(PolicyUpdater.validateSignedPolicies(null, configuration, domainPolicySignedData, "expiredDomain")); + } + + @Test + public void TestValidateExpiredPolicies_defaultConfDir() throws Exception { + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + try { + configuration.init(null, null); + Assert.fail(); + } catch (Exception e) { + } + } + + @Test + public void TestWritePolicies() throws Exception { + Path path = Paths.get("./src/test/resources/sys.auth.pol"); + DomainSignedPolicyData domainPolicySignedDataInput = JSON.fromBytes(Files.readAllBytes(path), DomainSignedPolicyData.class); + PolicyUpdater.writePolicies(pupConfig, "sys.auth", domainPolicySignedDataInput); + + path = Paths.get(pupConfig.getRootDir() + TEST_POLICY_DIR + "/sys.auth.pol"); + JSON.fromBytes(Files.readAllBytes(path), DomainSignedPolicyData.class); + + // test handling of missing tmp dir + // + Path sysauthPath = Paths.get(pupConfig.getRootDir() + TEST_POLICY_TEMP_DIR + "/tmp/sys.auth"); + Files.deleteIfExists(sysauthPath); + sysauthPath = Paths.get(pupConfig.getRootDir() + TEST_POLICY_TEMP_DIR + "/tmp"); + Files.deleteIfExists(sysauthPath); + java.io.File polFile = path.toFile(); + long flen = polFile.length(); + long fmod = polFile.lastModified(); + Thread.sleep(1000); + + System.setProperty(PolicyUpdaterConfiguration.ZPU_PROP_METRIC_FACTORY, "com.yahoo.athenz.zpe_policy_updater.MockMetricFactory"); + + PolicyUpdaterConfiguration config = new PolicyUpdaterConfiguration(); + config.init(pathToAthenzConfigFile, pathToZPUConfigFile); + config.setPolicyFileTmpDir(pupConfig.getRootDir() + TEST_POLICY_TEMP_DIR + "/tmp"); + config.setPolicyFileDir(pupConfig.getRootDir() + TEST_POLICY_DIR); + PolicyUpdater.writePolicies(config, "sys.auth", domainPolicySignedDataInput); + long flen2 = polFile.length(); + long fmod2 = polFile.lastModified(); + Assert.assertTrue(flen == flen2); + Assert.assertTrue(fmod < fmod2); + + // Test error handling for illegal arguments + boolean exceptionCaught = false; + try { + PolicyUpdater.writePolicies(null, "sys.auth", domainPolicySignedDataInput); + } catch (IllegalArgumentException ex) { + exceptionCaught = true; + } + Assert.assertTrue(exceptionCaught); + + exceptionCaught = false; + try { + config.setPolicyFileTmpDir(null); + PolicyUpdater.writePolicies(config, "sys.auth", domainPolicySignedDataInput); + } catch (IllegalArgumentException ex) { + exceptionCaught = true; + } + Assert.assertTrue(exceptionCaught); + + exceptionCaught = false; + try { + config.setPolicyFileTmpDir(TEST_POLICY_TEMP_DIR); + config.setPolicyFileDir(null); + PolicyUpdater.writePolicies(config, "sys.auth", domainPolicySignedDataInput); + } catch (IllegalArgumentException ex) { + exceptionCaught = true; + } + Assert.assertTrue(exceptionCaught); + + try { + config.setPolicyFileDir(TEST_POLICY_DIR); + PolicyUpdater.writePolicies(config, null, domainPolicySignedDataInput); + } catch (IllegalArgumentException ex) { + exceptionCaught = true; + } + Assert.assertTrue(exceptionCaught); + + try { + PolicyUpdater.writePolicies(config, "sys.auth", null); + } catch (IllegalArgumentException ex) { + exceptionCaught = true; + } + Assert.assertTrue(exceptionCaught); + + Files.delete(path); + + System.clearProperty(PolicyUpdaterConfiguration.ZPU_PROP_METRIC_FACTORY); + } + + @Test + public void TestGetEtagForExistingPolicy() throws Exception, IOException { + String expectedEtag = SignPoliciesUtility.signPolicies("./src/test/resources/zts_private_k0.pem", + "./src/test/resources/zms_private_k0.pem", "./src/test/resources/sys.auth.pol", + "./src/test/resources/sys.auth.new.pol"); + + Map newenv = new HashMap(); + newenv.put("STARTUP_DELAY", Integer.toString(TEST_STARTUP_DELAY)); + setEnvironmentVar(newenv); + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUConfigFile); + + String matchingTag = PolicyUpdater.getEtagForExistingPolicy(null, configuration, "sys.auth.new"); + Assert.assertEquals(matchingTag, expectedEtag); + + matchingTag = PolicyUpdater.getEtagForExistingPolicy(null, configuration, "sys.auth.new"); + Assert.assertEquals(matchingTag, expectedEtag); + } + + @Test + public void TestGetEtagForExistingPolicy_ExpirationLessThanStartUpDelay() throws Exception, IOException { + + Map newenv = new HashMap(); + newenv.put("STARTUP_DELAY", Integer.toString(TEST_STARTUP_DELAY)); + setEnvironmentVar(newenv); + System.setProperty(PolicyUpdaterConfiguration.ZPU_PROP_METRIC_FACTORY, "com.yahoo.athenz.zpe_policy_updater.MockMetricFactory"); + + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUConfigFile); + + String matchingTag = PolicyUpdater.getEtagForExistingPolicy(null, configuration, "sys.auth"); + Assert.assertNull(matchingTag); + + matchingTag = PolicyUpdater.getEtagForExistingPolicy(null, configuration, "sys.auth"); + Assert.assertNull(matchingTag); + + System.clearProperty(PolicyUpdaterConfiguration.ZPU_PROP_METRIC_FACTORY); + } + + @Test + public void TestGetEtagForExistingPolicy_NoPolicyFile() throws Exception { + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUConfigFile); + + // Negative test, getEtagForExistingPolicy should return null when no policy file is found + String matchingTag = PolicyUpdater.getEtagForExistingPolicy(null, configuration, "testDomain"); + Assert.assertNull(matchingTag); + } + + @Test + public void TestGetEtagForExistingPolicy_IllegalArgs() throws Exception { + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUConfigFile); + + // Test error handling for illegal arguments + boolean exceptionCaught = false; + + try { + PolicyUpdater.getEtagForExistingPolicy(null, configuration, null); + } catch (IllegalArgumentException ex) { + exceptionCaught = true; + } + Assert.assertTrue(exceptionCaught); + } + + @Test + public void TestGetEtagForExistingPolicy_BadPolicyFileDir_IllegalArgs() throws Exception { + + PolicyUpdaterConfiguration configMock = Mockito.mock(PolicyUpdaterConfiguration.class); + Mockito.when(configMock.getPolicyFileDir()).thenReturn(null); + + // Test error handling for illegal arguments + boolean exceptionCaught = false; + + try { + PolicyUpdater.getEtagForExistingPolicy(null, configMock, "testDomain"); + } catch (IllegalArgumentException ex) { + exceptionCaught = true; + } + Assert.assertTrue(exceptionCaught); + } + + @Test + public void TestPolicyUpdater() throws Exception { + System.setProperty(PolicyUpdaterConfiguration.ZPU_PROP_METRIC_FACTORY, "com.yahoo.athenz.zpe_policy_updater.MockMetricFactory"); + + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUEmptyConfigFile); + configuration.setPolicyFileDir(configuration.getRootDir() + TEST_POLICY_DIR); + configuration.setPolicyFileTmpDir(configuration.getRootDir() + TEST_POLICY_TEMP_DIR); + + DebugZTSClientFactory ztsFactory = new DebugZTSClientFactory(); + ztsFactory.setPublicKeyId("0"); + PolicyUpdater.policyUpdater(configuration, new DebugSIAClientFactory(), ztsFactory); + + Path path = Paths.get(configuration.getRootDir() + TEST_POLICY_DIR + File.separator + "sports.pol"); + DomainSignedPolicyData domainPolicySignedData = JSON.fromBytes(Files.readAllBytes(path), DomainSignedPolicyData.class); + + // Validate that the SignedPolicy written to target/classes is correct, return value is true when policies are correctly validated + Assert.assertTrue(PolicyUpdater.validateSignedPolicies(null, configuration, domainPolicySignedData, "sports")); + + Files.delete(path); + + path = Paths.get(configuration.getRootDir() + TEST_POLICY_DIR + File.separator + "sys.auth.pol"); + domainPolicySignedData = JSON.fromBytes(Files.readAllBytes(path), DomainSignedPolicyData.class); + + // Validate that the SignedPolicy written to target/classes is correct, return value is true when policies are correctly validated + Assert.assertTrue(PolicyUpdater.validateSignedPolicies(null, configuration, domainPolicySignedData, "sys.auth.pol")); + + Files.delete(path); + + System.clearProperty(PolicyUpdaterConfiguration.ZPU_PROP_METRIC_FACTORY); + } + + @Test + public void TestPolicyUpdaterEmptyDomainList() throws Exception { + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUEmptyConfigFile); + configuration.setPolicyFileDir("./target/classes"); + + PolicyUpdater.policyUpdater(configuration, new DebugSIAClientFactory(true), new DebugZTSClientFactory()); + + // Domain list was empty so no file should be found + Path path = Paths.get("./target/classes/sports.pol"); + try { + @SuppressWarnings("unused") + String fileBufferOutput = new String(Files.readAllBytes(path)); + Assert.fail(); + } catch (IOException ex) { + Assert.assertTrue(true); + } + } + + @Test + public void TestPolicyUpdaterSIANotHasDomainList() throws Exception { + + PolicyUpdaterConfiguration confMock = Mockito.mock(PolicyUpdaterConfiguration.class); + Mockito.when(confMock.getDomainList()).thenReturn(null); + + // sia has no domainList + SIA siaMock = Mockito.mock(SIA.class); + Mockito.when(siaMock.getDomainList()).thenReturn(null); + + SIAClientFactory siafactoryMock = Mockito.mock(SIAClientFactory.class); + Mockito.when(siafactoryMock.create()).thenReturn(siaMock); + + try { + PolicyUpdater.policyUpdater(confMock, siafactoryMock, new DebugZTSClientFactory()); + Assert.fail(); + } catch(Exception e) { + Assert.assertEquals(e.getMessage(), "no configured domains to process"); + } + } + + @Test + public void TestPolicyUpdaterZTSException() throws Exception { + System.setProperty(PolicyUpdaterConfiguration.ZPU_PROP_METRIC_FACTORY, "com.yahoo.athenz.zpe_policy_updater.MockMetricFactory"); + + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUEmptyConfigFile); + configuration.setPolicyFileDir(configuration.getRootDir() + TEST_POLICY_DIR); + configuration.setPolicyFileTmpDir(configuration.getRootDir() + TEST_POLICY_TEMP_DIR); + + DebugZTSClientFactory ztsFactory = new DebugZTSClientFactory(); + ztsFactory.setPublicKeyId("4"); // not exist id + PolicyUpdater.policyUpdater(configuration, new DebugSIAClientFactory(), ztsFactory); + } + + @Test + public void TestPolicyUpdaterConfiguraturationZTSKeyRetrieval() throws Exception { + + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.init(pathToAthenzConfigFile, pathToZPUConfigFile); + Assert.assertNotNull(configuration.getZtsPublicKey(null, "0")); + Assert.assertNotNull(configuration.getZtsPublicKey(null, "1")); + Assert.assertNull(configuration.getZtsPublicKey(null, "2")); + } + + // URI null is not allow + @Test(expectedExceptions={java.lang.NullPointerException.class}) + public void TestZTSClientFactoryImpl() { + ZTSClientFactoryImpl factory = new ZTSClientFactoryImpl(); + factory.create(); + } + + @Test + public void TestSIAClientFactoryImpl() { + SIAClientFactoryImpl siafactory = new SIAClientFactoryImpl(); + Assert.assertEquals(siafactory.create().getClass(), SIAClient.class); + } + + @Test + public void TestPolicyUpdaterConfigGetSet() { + PolicyUpdaterConfiguration configuration = new PolicyUpdaterConfiguration(); + configuration.setRootDir("/home/root"); + configuration.setDebugMode(true); + configuration.setStartupDelayInterval(2); + + Assert.assertEquals(configuration.getRootDir(), "/home/root"); + Assert.assertEquals(configuration.isDebugMode(), true); + Assert.assertEquals(configuration.getStartupDelayIntervalInSecs(), 2*60); + + } + + @Test + public void TestverifyTmpDirSetupUserNull() throws Exception { + System.setProperty(PolicyUpdaterConfiguration.ZPU_PROP_METRIC_FACTORY, "com.yahoo.athenz.zpe_policy_updater.MockMetricFactory"); + + PolicyUpdaterConfiguration config = Mockito.mock(PolicyUpdaterConfiguration.class); + Mockito.when(config.getPolicyFileTmpDir()).thenReturn(pupConfig.getRootDir() + TEST_POLICY_TEMP_DIR + "/tmpnon"); + Mockito.when(config.getZpuDirOwner()).thenReturn(null); + + boolean expectedCaught = true; + try { + PolicyUpdater.verifyTmpDirSetup(config); + } catch (Exception e) { + } + Assert.assertTrue(expectedCaught); + } + + private void setEnvironmentVar(Map newenv) throws Exception { + @SuppressWarnings("rawtypes") + Class[] classes = Collections.class.getDeclaredClasses(); + Map env = System.getenv(); + for (@SuppressWarnings("rawtypes") Class cl : classes) { + if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) { + Field field = cl.getDeclaredField("m"); + field.setAccessible(true); + Object obj = field.get(env); + @SuppressWarnings("unchecked") + Map map = (Map) obj; + map.clear(); + map.putAll(newenv); + } + } + } +} diff --git a/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/SIAClientMock.java b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/SIAClientMock.java new file mode 100644 index 00000000000..e33bafcc2cf --- /dev/null +++ b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/SIAClientMock.java @@ -0,0 +1,103 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yahoo.athenz.zpe_policy_updater; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; +import java.util.ArrayList; +import java.util.List; + +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.PrincipalAuthority; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.token.PrincipalToken; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.sia.SIA; + +public class SIAClientMock implements SIA { + + private final String svcVersion = "S1"; + private final String host = "somehost.somecompany.com"; + private final String salt = "saltvalue"; + private final long expirationTime = 10; // 10 seconds + private PrivateKey servicePrivateKeyK0 = null; + private PrivateKey servicePrivateKeyK1 = null; + private String keyId = "0"; + + private List domainList = new ArrayList(); + + public SIAClientMock(boolean emptyDomainList) throws IOException + { + loadKeys(); + + if (emptyDomainList) { + setDomainListEmpty(); + } else { + setDomainList(); + } + } + + public void setDomainListEmpty() { + domainList.clear(); + } + + public void setDomainList() { + domainList.add("sports"); + domainList.add("sys.auth"); + } + + private void loadKeys() throws IOException { + Path path = Paths.get("./src/test/resources/zts_private_k0.pem"); + servicePrivateKeyK0 = Crypto.loadPrivateKey(new String(Files.readAllBytes(path))); + + path = Paths.get("./src/test/resources/zts_private_k1.pem"); + servicePrivateKeyK1 = Crypto.loadPrivateKey(new String(Files.readAllBytes(path))); + } + + public void setPublicKeyId(String keyId) { + this.keyId = keyId; + } + + @Override + public ArrayList getDomainList() throws IOException { + return (ArrayList) domainList; + } + + @Override + public Principal getServicePrincipal(String domain, String service, + Integer minExpiryTime, Integer maxExpiryTime, boolean ignoreCache) throws IOException { + // Create and sign token + PrincipalToken token = new PrincipalToken.Builder(svcVersion, domain, service) + .host(host).salt(salt).issueTime(System.currentTimeMillis()) + .expirationWindow(expirationTime).keyId(keyId).build(); + + if ("0".equals(keyId)) { + token.sign(servicePrivateKeyK0); + }else if ("1".equals(keyId)) { + token.sign(servicePrivateKeyK1); + } + + Principal principal = SimplePrincipal.create(domain, service, token.getSignedToken(), new PrincipalAuthority()); + + // Create a token for validation using the signed data + return principal; + } + +} diff --git a/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/SignPoliciesUtility.java b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/SignPoliciesUtility.java new file mode 100644 index 00000000000..53a6947c3e3 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/SignPoliciesUtility.java @@ -0,0 +1,94 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; + +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.utils.SignUtils; +import com.yahoo.athenz.zts.DomainSignedPolicyData; +import com.yahoo.athenz.zts.PolicyData; +import com.yahoo.athenz.zts.SignedPolicyData; +import com.yahoo.rdl.JSON; +import com.yahoo.rdl.Timestamp; + +/** + * Sign policy files + * + */ +public class SignPoliciesUtility +{ + private static void usage() { + System.out.println("usage: java -cp "); + System.exit(-1); + } + + static String signPolicies(String ztsPrivateKeyPath, String zmsPrivateKeyPath, String signedPolicyFile, + String newPolicyFile) { + + String etag = null; + try { + Path path = Paths.get(ztsPrivateKeyPath); + PrivateKey ztsPrivateKey = Crypto.loadPrivateKey(new String((Files.readAllBytes(path)))); + + path = Paths.get(zmsPrivateKeyPath); + PrivateKey zmsPrivateKey = Crypto.loadPrivateKey(new String((Files.readAllBytes(path)))); + + path = Paths.get(signedPolicyFile); + DomainSignedPolicyData domainSignedPolicyData = JSON.fromBytes(Files.readAllBytes(path), + DomainSignedPolicyData.class); + SignedPolicyData signedPolicyData = domainSignedPolicyData.getSignedPolicyData(); + + PolicyData policyData = signedPolicyData.getPolicyData(); + signedPolicyData.setZmsSignature(Crypto.sign(SignUtils.asCanonicalString(policyData), zmsPrivateKey)); + signedPolicyData.setZmsKeyId("0"); + + long curTime = System.currentTimeMillis(); + Timestamp modified = Timestamp.fromMillis(curTime); + signedPolicyData.setModified(modified); + + Timestamp expires = Timestamp.fromMillis(curTime + (1000L * 60 * 60 * 24 * 7)); + signedPolicyData.setExpires(expires); + + String signature = Crypto.sign(SignUtils.asCanonicalString(signedPolicyData), ztsPrivateKey); + domainSignedPolicyData.setSignature(signature).setKeyId("0"); + File file = new File(newPolicyFile); + file.createNewFile(); + Files.write(file.toPath(), JSON.bytes(domainSignedPolicyData)); + + etag = "\"" + modified.toString() + "\""; + } catch (IOException e) { + System.out.println("Exception: " + e.getMessage()); + System.exit(-1); + } + + System.out.println("Signed " + newPolicyFile + " policy file"); + return etag; + } + + public static void main(String[] args) { + if (args.length != 4) { + usage(); + } + + signPolicies(args[0], args[1], args[2], args[3]); + } +} diff --git a/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/ZTSMock.java b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/ZTSMock.java new file mode 100644 index 00000000000..751b86b4d4f --- /dev/null +++ b/utils/zpe_policy_updater/src/test/java/com/yahoo/athenz/zpe_policy_updater/ZTSMock.java @@ -0,0 +1,238 @@ +/** + * Copyright 2016 Yahoo Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yahoo.athenz.zpe_policy_updater; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.yahoo.rdl.Timestamp; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.utils.SignUtils; +import com.yahoo.athenz.zts.AWSCertificateRequest; +import com.yahoo.athenz.zts.AWSInstanceInformation; +import com.yahoo.athenz.zts.AWSTemporaryCredentials; +import com.yahoo.athenz.zts.Access; +import com.yahoo.athenz.zts.Assertion; +import com.yahoo.athenz.zts.AssertionEffect; +import com.yahoo.athenz.zts.DomainMetrics; +import com.yahoo.athenz.zts.DomainSignedPolicyData; +import com.yahoo.athenz.zts.HostServices; +import com.yahoo.athenz.zts.Identity; +import com.yahoo.athenz.zts.InstanceInformation; +import com.yahoo.athenz.zts.InstanceRefreshRequest; +import com.yahoo.athenz.zts.Policy; +import com.yahoo.athenz.zts.PolicyData; +import com.yahoo.athenz.zts.PublicKeyEntry; +import com.yahoo.athenz.zts.RoleToken; +import com.yahoo.athenz.zts.ServiceIdentity; +import com.yahoo.athenz.zts.ServiceIdentityList; +import com.yahoo.athenz.zts.SignedPolicyData; +import com.yahoo.athenz.zts.TenantDomains; +import com.yahoo.athenz.zts.ZTSClientException; +import com.yahoo.athenz.zts.ZTSRDLGeneratedClient; + +public class ZTSMock extends ZTSRDLGeneratedClient { + private PrivateKey ztsPrivateKeyK0 = null; + private PrivateKey ztsPrivateKeyK1 = null; + private PrivateKey zmsPrivateKeyK0 = null; + private String keyId = "0"; + + public ZTSMock() throws IOException { + super("http://localhost:10080"); + loadKeys(); + } + + private void loadKeys() throws IOException { + Path path = Paths.get("./src/test/resources/zts_private_k0.pem"); + ztsPrivateKeyK0 = Crypto.loadPrivateKey(new String(Files.readAllBytes(path))); + + path = Paths.get("./src/test/resources/zts_private_k1.pem"); + ztsPrivateKeyK1 = Crypto.loadPrivateKey(new String(Files.readAllBytes(path))); + + path = Paths.get("./src/test/resources/zms_private_k0.pem"); + zmsPrivateKeyK0 = Crypto.loadPrivateKey(new String(Files.readAllBytes(path))); + } + + void setPublicKeyId(String keyId) { + this.keyId = keyId; + } + + @Override + public DomainSignedPolicyData getDomainSignedPolicyData(String domainName, + String matchingTag, Map> responseHeaders) { + + DomainSignedPolicyData result = null; + if (!domainName.equals("sports") && + !domainName.equals("sys.auth") && + !domainName.equals("expiredDomain")) { + return result; + } + + SignedPolicyData signedPolicyData = new SignedPolicyData(); + + Timestamp expires; + if (domainName.equals("expiredDomain")) { + expires = Timestamp.fromMillis(System.currentTimeMillis() + - (1000L * 60)); + } else { + expires = Timestamp.fromMillis(System.currentTimeMillis() + + (1000L * 60 * 60 * 24 * 7)); + } + signedPolicyData.setExpires(expires); + + Timestamp modified = Timestamp.fromMillis(System.currentTimeMillis()); + signedPolicyData.setModified(modified); + + String policyName = domainName + ":policy." + "admin"; + Policy policy = new Policy(); + policy.setName(policyName); + + Assertion assertion = new Assertion(); + assertion.setAction("*"); + assertion.setEffect(AssertionEffect.ALLOW); + assertion.setResource("*"); + + String roleName = domainName + ":role." + "admin"; + assertion.setRole(roleName); + + List assertList = new ArrayList(); + assertList.add(assertion); + + assertion = new Assertion(); + assertion.setAction("*"); + assertion.setEffect(AssertionEffect.DENY); + assertion.setResource("*"); + + roleName = domainName + ":role." + "non-admin"; + assertion.setRole(roleName); + assertList.add(assertion); + + policy.setAssertions(assertList); + + List listOfPolicies = new ArrayList(); + listOfPolicies.add(policy); + + PolicyData policyData = new PolicyData(); + policyData.setPolicies(listOfPolicies); + policyData.setDomain(domainName); + + signedPolicyData.setPolicyData(policyData); + signedPolicyData.setZmsKeyId("0"); + signedPolicyData.setZmsSignature(Crypto.sign(SignUtils.asCanonicalString(policyData), zmsPrivateKeyK0)); + + DomainSignedPolicyData domainSignedPolicyData = new DomainSignedPolicyData(); + domainSignedPolicyData.setSignedPolicyData(signedPolicyData); + + PrivateKey ztsKey = null; + if ("0".equals(keyId)) { + ztsKey = ztsPrivateKeyK0; + } else if ("1".equals(keyId)) { + ztsKey = ztsPrivateKeyK1; + } + + String signature = Crypto.sign(SignUtils.asCanonicalString(signedPolicyData), ztsKey); + domainSignedPolicyData.setKeyId(keyId); + domainSignedPolicyData.setSignature(signature); + + return domainSignedPolicyData; + } + + @Override + public HostServices getHostServices(String arg0) { + return null; + } + + @Override + public RoleToken getRoleToken(String domainName, String role, + Integer minExpiryTime, Integer maxExpiryTime, String proxyForPrincipal) { + return null; + } + + @Override + public ServiceIdentity getServiceIdentity(String domainName, String serviceName) { + return null; + } + + @Override + public ServiceIdentityList getServiceIdentityList(String domainName) { + return null; + } + + @Override + public PublicKeyEntry getPublicKeyEntry(String domainName, String serviceName, + String keyId) { + PublicKeyEntry keyEntry = null; + if ("2".equals(keyId)) { + keyEntry = new PublicKeyEntry(); + Path path = Paths.get("./src/test/resources/zts_public_k1.pem"); + keyEntry.setId(keyId); + try { + keyEntry.setKey(Crypto.ybase64(Files.readAllBytes(path))); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (keyEntry == null) { + throw new ZTSClientException(404, "Unknown ZTS Public Key"); + } else { + return keyEntry; + } + } + + @Override + public AWSTemporaryCredentials getAWSTemporaryCredentials(String domainName, String roleName) { + return null; + } + @Override + public Identity postInstanceInformation(InstanceInformation info) { + return null; + } + @Override + public TenantDomains getTenantDomains(String providerDomainName, String userName, String roleName, String serviceName) { + return null; + } + + @Override + public Access getAccess(String arg0, String arg1, String arg2) { + return null; + } + + @Override + public Identity postAWSCertificateRequest(String arg0, String arg1, AWSCertificateRequest arg2) { + return null; + } + + @Override + public Identity postAWSInstanceInformation(AWSInstanceInformation arg0) { + return null; + } + + @Override + public DomainMetrics postDomainMetrics(String arg0, DomainMetrics arg1) { + return null; + } + + @Override + public Identity postInstanceRefreshRequest(String arg0, String arg1, InstanceRefreshRequest arg2) { + return null; + } +} diff --git a/utils/zpe_policy_updater/src/test/resources/athenz.conf b/utils/zpe_policy_updater/src/test/resources/athenz.conf new file mode 100755 index 00000000000..2ec9767b4f1 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/athenz.conf @@ -0,0 +1,24 @@ +{ + "zmsUrl": "http://dev.zms.athenzcompany.com:4443/", + "ztsUrl": "https://dev.zts.athenzcompany.com:4443/", + "ztsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + }, + { + "id": "1", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBUFpyWkMxelBhNXBQZloxaXFtcjdnWW9YaHVIbGlSUApVbnlLelliWWhRZXpUSlJlSDBsdWhvVVdQdTZxeWRHSm54RVUyTldNQ1hZLzhuL1VGSUZvakYwQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + } + ], + "zmsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + }, + { + "id": "1", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTHpmU09UUUpmRW0xZW00TDNza3lOVlEvYngwTU9UcQphK1J3T0gzWmNNS3lvR3hPSm85QXllUmE2RlhNbXZKSkdZczVQMzRZc3pGcG5qMnVBYmkyNG5FQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo-" + } + ] +} diff --git a/utils/zpe_policy_updater/src/test/resources/logback.xml b/utils/zpe_policy_updater/src/test/resources/logback.xml new file mode 100644 index 00000000000..2ce05abfc06 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + System.out + + %d{HH:mm:ss.SSS} [%thread] %-5level %class - %msg%n + + + + + + + + + + diff --git a/utils/zpe_policy_updater/src/test/resources/pol_dir/.keepme b/utils/zpe_policy_updater/src/test/resources/pol_dir/.keepme new file mode 100644 index 00000000000..e69de29bb2d diff --git a/utils/zpe_policy_updater/src/test/resources/sports.pol b/utils/zpe_policy_updater/src/test/resources/sports.pol new file mode 100644 index 00000000000..bd92c395671 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/sports.pol @@ -0,0 +1,28 @@ +{ + "contents": { + "domain": "sports", + "modified": "2015-01-13T19:13:18.601Z", + "policies": [ + { + "name": "sports:policy.admin", + "assertions": [ + { + "resource": "*", + "role": "sports:role.admin", + "action": "*", + "effect": "ALLOW" + }, + { + "resource": "*", + "role": "sports:role.non-admin", + "action": "*", + "effect": "DENY" + } + ] + } + ], + "expires": "2015-01-20T19:13:18.599Z" + }, + "signature": "qJXtTXPI0aQcxUgerT9X4BM6HoKVM1pNClt86UViintmC5.IK9uYoFhtdMfDLq7Kzc8jdyT1iCqv_OmEyMHLCA--", + "keyId": "0" +} diff --git a/utils/zpe_policy_updater/src/test/resources/sys.auth.pol b/utils/zpe_policy_updater/src/test/resources/sys.auth.pol new file mode 100644 index 00000000000..5031cfe7bad --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/sys.auth.pol @@ -0,0 +1,32 @@ +{ + "signedPolicyData": { + "modified": "2015-01-13T19:13:37.745Z", + "policyData": { + "domain": "sys.auth", + "policies": [ + { + "name": "sys.auth:policy.admin", + "assertions": [ + { + "resource": "*", + "role": "sys.auth:role.admin", + "action": "*", + "effect": "ALLOW" + }, + { + "resource": "*", + "role": "sys.auth:role.non-admin", + "action": "*", + "effect": "DENY" + } + ] + } + ] + }, + "expires": "2015-01-20T19:13:37.744Z", + "zmsSignature": "eN5aZiqCof3HjHctjkYB2itNvI.90u.bq6zzLoXG8CJ8hSwG6IBMaIQ0yf.Q_SR5HTFi7Tmrc1quQuH17H0PJg--", + "zmsKeyId": "0" + }, + "signature": "eN5aZiqCof3HjHctjkYB2itNvI.90u.bq6zzLoXG8CJ8hSwG6IBMaIQ0yf.Q_SR5HTFi7Tmrc1quQuH17H0PJg--", + "keyId": "0" +} diff --git a/utils/zpe_policy_updater/src/test/resources/sys.auth.pol.tampered.zms b/utils/zpe_policy_updater/src/test/resources/sys.auth.pol.tampered.zms new file mode 100644 index 00000000000..a02d42c273a --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/sys.auth.pol.tampered.zms @@ -0,0 +1,32 @@ +{ + "signature": "e7Nuk70agdFv54UOWf4EauTYuAQ3ww_G5A_5OiBt22DQVv23DVTCLovtWNBdzcsmDgdDAxGwWO2CG1T4uT0KhA--", + "keyId": "0", + "signedPolicyData": { + "zmsSignature": "Y2HuXmgL86PL1WnleGFHwPmNEqUdWgDxmmIsDnF5f5oqakacqTtwt9JNqDV9nuJ7LnKl3zsZoDQSAtcHMu4IGA--", + "zmsKeyId": "0", + "expires": "2015-10-16T05:29:29.507Z", + "policyData": { + "policies": [ + { + "name": "sys.auth:policy.admin", + "assertions": [ + { + "resource": "*", + "role": "sys.auth:role.admin", + "action": "*", + "effect": "DENY" + }, + { + "resource": "*", + "role": "sys.auth:role.non-admin", + "action": "*", + "effect": "ALLOW" + } + ] + } + ], + "domain": "sys.auth" + }, + "modified": "2015-10-09T05:29:29.507Z" + } +} diff --git a/utils/zpe_policy_updater/src/test/resources/sys.auth.pol.tampered.zts b/utils/zpe_policy_updater/src/test/resources/sys.auth.pol.tampered.zts new file mode 100644 index 00000000000..43a15a2c9c4 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/sys.auth.pol.tampered.zts @@ -0,0 +1,32 @@ +{ + "signature": "e7Nuk70agdFv54UOWf4EauTYuAQ3ww_G5A_5OiBt22DQVv23DVTCLovtWNBdzcsmDgdDAxGwWO2CG1T4uT0KhA--", + "keyId": "0", + "signedPolicyData": { + "zmsSignature": "Y2HuXmgL86PL1WnleGFHwPmNEqUdWgDxmmIsDnF5f5oqakacqTtwt9JNqDV9nuJ7LnKl3zsZoDQSAtcHMu4IGA--", + "zmsKeyId": "0", + "expires": "2015-10-16T05:29:28.507Z", + "policyData": { + "policies": [ + { + "name": "sys.auth:policy.admin", + "assertions": [ + { + "resource": "*", + "role": "sys.auth:role.admin", + "action": "*", + "effect": "ALLOW" + }, + { + "resource": "*", + "role": "sys.auth:role.non-admin", + "action": "*", + "effect": "DENY" + } + ] + } + ], + "domain": "sys.auth" + }, + "modified": "2015-10-09T05:29:29.507Z" + } +} diff --git a/utils/zpe_policy_updater/src/test/resources/tmp/.keepme b/utils/zpe_policy_updater/src/test/resources/tmp/.keepme new file mode 100644 index 00000000000..e69de29bb2d diff --git a/utils/zpe_policy_updater/src/test/resources/zms_private_k0.pem b/utils/zpe_policy_updater/src/test/resources/zms_private_k0.pem new file mode 100644 index 00000000000..356e1edf8bd --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/zms_private_k0.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTqa+RwOH3ZcMKyoGxOJo9A +yeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQJARpQBv09w/j6O7TmgtJm4 +Ws5bIxMgOkrHaqPs2Epq8rX8FUyaYSHBL4yYhqWlno4j1TrLzWneni8kJCUNJydU +AQIhAPXBXPQ3UzzNeQC5gbcweMMIQOYHyF7W/NAEILSKgy6hAiEAxL7hOjpdYGtj +hjG1nVIs+HW6z5TnRig7JjTwqA8RMdECIQC4oPCIuRfb0jJaDQQa8FuJiqXXK3mp +ZrLARJmdiYJMgQIgZxpAnWsIlAay2RgjvJXbyzim9TFrIXDjzlnf47JBqIECIBue +3ht9BnawOQqGzFjD89dFQ6nZtbiAaqORj276/SzZ +-----END RSA PRIVATE KEY----- diff --git a/utils/zpe_policy_updater/src/test/resources/zms_public_k0.pem b/utils/zpe_policy_updater/src/test/resources/zms_public_k0.pem new file mode 100644 index 00000000000..727c3c67f22 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/zms_public_k0.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTq +a+RwOH3ZcMKyoGxOJo9AyeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQ== +-----END PUBLIC KEY----- diff --git a/utils/zpe_policy_updater/src/test/resources/zpu.conf b/utils/zpe_policy_updater/src/test/resources/zpu.conf new file mode 100644 index 00000000000..73e9af4948e --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/zpu.conf @@ -0,0 +1,4 @@ +{ + "domains": "athenz.ci,coretech.hosted", + "user": "root" +} diff --git a/utils/zpe_policy_updater/src/test/resources/zpu_empty.conf b/utils/zpe_policy_updater/src/test/resources/zpu_empty.conf new file mode 100644 index 00000000000..0e09533bca3 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/zpu_empty.conf @@ -0,0 +1,3 @@ +{ + "domains": "" +} diff --git a/utils/zpe_policy_updater/src/test/resources/zpu_private.pem b/utils/zpe_policy_updater/src/test/resources/zpu_private.pem new file mode 100644 index 00000000000..356e1edf8bd --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/zpu_private.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTqa+RwOH3ZcMKyoGxOJo9A +yeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQJARpQBv09w/j6O7TmgtJm4 +Ws5bIxMgOkrHaqPs2Epq8rX8FUyaYSHBL4yYhqWlno4j1TrLzWneni8kJCUNJydU +AQIhAPXBXPQ3UzzNeQC5gbcweMMIQOYHyF7W/NAEILSKgy6hAiEAxL7hOjpdYGtj +hjG1nVIs+HW6z5TnRig7JjTwqA8RMdECIQC4oPCIuRfb0jJaDQQa8FuJiqXXK3mp +ZrLARJmdiYJMgQIgZxpAnWsIlAay2RgjvJXbyzim9TFrIXDjzlnf47JBqIECIBue +3ht9BnawOQqGzFjD89dFQ6nZtbiAaqORj276/SzZ +-----END RSA PRIVATE KEY----- diff --git a/utils/zpe_policy_updater/src/test/resources/zpu_public.pem b/utils/zpe_policy_updater/src/test/resources/zpu_public.pem new file mode 100644 index 00000000000..727c3c67f22 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/zpu_public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTq +a+RwOH3ZcMKyoGxOJo9AyeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQ== +-----END PUBLIC KEY----- diff --git a/utils/zpe_policy_updater/src/test/resources/zts_private_k0.pem b/utils/zpe_policy_updater/src/test/resources/zts_private_k0.pem new file mode 100644 index 00000000000..356e1edf8bd --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/zts_private_k0.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTqa+RwOH3ZcMKyoGxOJo9A +yeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQJARpQBv09w/j6O7TmgtJm4 +Ws5bIxMgOkrHaqPs2Epq8rX8FUyaYSHBL4yYhqWlno4j1TrLzWneni8kJCUNJydU +AQIhAPXBXPQ3UzzNeQC5gbcweMMIQOYHyF7W/NAEILSKgy6hAiEAxL7hOjpdYGtj +hjG1nVIs+HW6z5TnRig7JjTwqA8RMdECIQC4oPCIuRfb0jJaDQQa8FuJiqXXK3mp +ZrLARJmdiYJMgQIgZxpAnWsIlAay2RgjvJXbyzim9TFrIXDjzlnf47JBqIECIBue +3ht9BnawOQqGzFjD89dFQ6nZtbiAaqORj276/SzZ +-----END RSA PRIVATE KEY----- diff --git a/utils/zpe_policy_updater/src/test/resources/zts_private_k1.pem b/utils/zpe_policy_updater/src/test/resources/zts_private_k1.pem new file mode 100644 index 00000000000..5d5faa50a71 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/zts_private_k1.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAPZrZC1zPa5pPfZ1iqmr7gYoXhuHliRPUnyKzYbYhQezTJReH0lu +hoUWPu6qydGJnxEU2NWMCXY/8n/UFIFojF0CAwEAAQJAALfZZ+SBFoQKATDggZQV +soDlnVDs2Bg/FkvVQ4JYCOi0ZqOGjsqplvw7vfML14zPbT7g7gnFCT5TXYzO8qYB +xQIhAPzUNTbMbiQCf5leOn5tLj5imgDtFfy185wbjIt1Sx/DAiEA+YKakkhvHqBP ++cJQaBRneLt5RJXDsEB7xc3uaER4AV8CIHYApYWaDJ4J/Hwcmrh/ROIhKzfbcDOu +yLDHuuUsLY/5AiEAt+LVYHIZ0wx7ZKsc71f6WjRwz2dA7ajYj5OR3S548ykCIQCC +W9QxTqfLoDGTj3Oe5rSyDquXyOz+45hfsZCuLniQkA== +-----END RSA PRIVATE KEY----- diff --git a/utils/zpe_policy_updater/src/test/resources/zts_public_k0.pem b/utils/zpe_policy_updater/src/test/resources/zts_public_k0.pem new file mode 100644 index 00000000000..727c3c67f22 --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/zts_public_k0.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALzfSOTQJfEm1em4L3skyNVQ/bx0MOTq +a+RwOH3ZcMKyoGxOJo9AyeRa6FXMmvJJGYs5P34YszFpnj2uAbi24nECAwEAAQ== +-----END PUBLIC KEY----- diff --git a/utils/zpe_policy_updater/src/test/resources/zts_public_k1.pem b/utils/zpe_policy_updater/src/test/resources/zts_public_k1.pem new file mode 100644 index 00000000000..3c715a4b69d --- /dev/null +++ b/utils/zpe_policy_updater/src/test/resources/zts_public_k1.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPZrZC1zPa5pPfZ1iqmr7gYoXhuHliRP +UnyKzYbYhQezTJReH0luhoUWPu6qydGJnxEU2NWMCXY/8n/UFIFojF0CAwEAAQ== +-----END PUBLIC KEY-----