killbill-aplcache
Changes
NEWS 3(+3 -0)
profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/LoggingFilterObfuscator.java 57(+57 -0)
profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/ObfuscatorConverter.java 1(+1 -0)
profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java 4(+4 -0)
profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestLoggingFilterObfuscator.java 81(+81 -0)
util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java 95(+61 -34)
Details
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index efd0828..2ba796a 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -121,6 +121,7 @@ import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.billing.util.config.definition.JaxrsConfig;
import org.killbill.billing.util.config.definition.PaymentConfig;
+import org.killbill.billing.util.customfield.CustomField;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.billing.util.tag.Tag;
@@ -1190,6 +1191,25 @@ public class AccountResource extends JaxRsResourceBase {
}
@TimedResource
+ @GET
+ @Path("/{accountId:" + UUID_PATTERN + "}/" + ALL_CUSTOM_FIELDS)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Retrieve account customFields", response = CustomFieldJson.class, responseContainer = "List")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account id supplied"),
+ @ApiResponse(code = 404, message = "Account not found")})
+ public Response getAllCustomFields(@PathParam(ID_PARAM_NAME) final String accountIdString,
+ @QueryParam(QUERY_OBJECT_TYPE) final ObjectType objectType,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @javax.ws.rs.core.Context final HttpServletRequest request) {
+ final UUID accountId = UUID.fromString(accountIdString);
+ final TenantContext tenantContext = context.createContext(request);
+ final List<CustomField> customFields = objectType != null ?
+ customFieldUserApi.getCustomFieldsForAccountType(accountId, objectType, tenantContext) :
+ customFieldUserApi.getCustomFieldsForAccount(accountId, tenantContext);
+ return createCustomFieldResponse(customFields, auditMode, tenantContext);
+ }
+
+ @TimedResource
@POST
@Path("/{accountId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index 38a3986..5bb10b5 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -221,6 +221,7 @@ public interface JaxrsResource {
public static final String TAGS = "tags";
public static final String TAGS_PATH = PREFIX + "/" + TAGS;
+ public static final String ALL_CUSTOM_FIELDS = "allCustomFields";
public static final String CUSTOM_FIELDS = "customFields";
public static final String CUSTOM_FIELDS_PATH = PREFIX + "/" + CUSTOM_FIELDS;
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 1e139a7..97c9b8c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -241,8 +241,11 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
protected Response getCustomFields(final UUID id, final AuditMode auditMode, final TenantContext context) {
final List<CustomField> fields = customFieldUserApi.getCustomFieldsForObject(id, getObjectType(), context);
+ return createCustomFieldResponse(fields, auditMode, context);
+ }
- final List<CustomFieldJson> result = new LinkedList<CustomFieldJson>();
+ protected Response createCustomFieldResponse(final Iterable<CustomField> fields, final AuditMode auditMode, final TenantContext context) {
+ final Collection<CustomFieldJson> result = new LinkedList<CustomFieldJson>();
for (final CustomField cur : fields) {
// TODO PIERRE - Bulk API
final List<AuditLog> auditLogs = auditUserApi.getAuditLogs(cur.getId(), ObjectType.CUSTOM_FIELD, auditMode.getLevel(), context);
NEWS 3(+3 -0)
diff --git a/NEWS b/NEWS
index e3a0cb7..c1e7da5 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,6 @@
+0.18.13
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.13
+
0.18.12
See https://github.com/killbill/killbill/releases/tag/killbill-0.18.12
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/LoggingFilterObfuscator.java b/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/LoggingFilterObfuscator.java
new file mode 100644
index 0000000..536682f
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/LoggingFilterObfuscator.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.log.obfuscators;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.regex.Pattern;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import com.google.common.collect.ImmutableList;
+
+public class LoggingFilterObfuscator extends Obfuscator {
+
+ private static final String[] DEFAULT_SENSITIVE_HEADERS = {
+ "Authorization",
+ "X-Killbill-ApiSecret",
+ };
+
+ private final Collection<Pattern> patterns = new LinkedList<Pattern>();
+
+ public LoggingFilterObfuscator() {
+ this(ImmutableList.<Pattern>of());
+ }
+
+ public LoggingFilterObfuscator(final Collection<Pattern> extraPatterns) {
+ super();
+
+ for (final String sensitiveKey : DEFAULT_SENSITIVE_HEADERS) {
+ this.patterns.add(buildPattern(sensitiveKey));
+ }
+ this.patterns.addAll(extraPatterns);
+ }
+
+ @Override
+ public String obfuscate(final String originalString, final ILoggingEvent event) {
+ return obfuscate(originalString, patterns, event);
+ }
+
+ private Pattern buildPattern(final String key) {
+ return Pattern.compile("\\s*" + key + ":\\s*([^\\n]+)", DEFAULT_PATTERN_FLAGS);
+ }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/ObfuscatorConverter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/ObfuscatorConverter.java
index 39b9306..6e522d6 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/ObfuscatorConverter.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/ObfuscatorConverter.java
@@ -45,6 +45,7 @@ import com.google.common.collect.ImmutableList;
public class ObfuscatorConverter extends ClassicConverter {
private final Collection<Obfuscator> obfuscators = ImmutableList.<Obfuscator>of(new ConfigMagicObfuscator(),
+ new LoggingFilterObfuscator(),
new PatternObfuscator(),
new LuhnMaskingObfuscator());
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
index 49057fb..eaf9e55 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
@@ -44,6 +44,7 @@ import org.killbill.billing.util.glue.KillBillShiroModule;
import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
+import org.killbill.billing.util.security.shiro.realm.KillBillOktaRealm;
import org.skife.config.ConfigSource;
import org.skife.config.ConfigurationObjectFactory;
@@ -87,6 +88,9 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
if (KillBillShiroModule.isLDAPEnabled()) {
bindRealm().to(KillBillJndiLdapRealm.class).asEagerSingleton();
}
+ if (KillBillShiroModule.isOktaEnabled()) {
+ bindRealm().to(KillBillOktaRealm.class).asEagerSingleton();
+ }
bindListener(new AbstractMatcher<TypeLiteral<?>>() {
@Override
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCustomField.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCustomField.java
index 66a3af7..123bb59 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCustomField.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCustomField.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2014 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -27,6 +27,7 @@ import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.model.Account;
import org.killbill.billing.client.model.CustomField;
import org.killbill.billing.client.model.CustomFields;
+import org.killbill.billing.util.api.AuditLevel;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -39,13 +40,13 @@ public class TestCustomField extends TestJaxrsBase {
final CustomField customField = new CustomField();
customField.setName(UUID.randomUUID().toString().substring(0, 5));
customField.setValue(UUID.randomUUID().toString().substring(0, 5));
- killBillClient.createAccountCustomField(account.getAccountId(), customField, createdBy, reason, comment);
+ killBillClient.createAccountCustomField(account.getAccountId(), customField, requestOptions);
}
- final CustomFields allCustomFields = killBillClient.getCustomFields();
+ final CustomFields allCustomFields = killBillClient.getCustomFields(requestOptions);
Assert.assertEquals(allCustomFields.size(), 5);
- CustomFields page = killBillClient.getCustomFields(0L, 1L);
+ CustomFields page = killBillClient.getCustomFields(0L, 1L, requestOptions);
for (int i = 0; i < 5; i++) {
Assert.assertNotNull(page);
Assert.assertEquals(page.size(), 1);
@@ -60,15 +61,21 @@ public class TestCustomField extends TestJaxrsBase {
doSearchCustomField(customField.getValue(), customField);
}
- final CustomFields customFields = killBillClient.searchCustomFields(ObjectType.ACCOUNT.toString());
+ final CustomFields customFields = killBillClient.searchCustomFields(ObjectType.ACCOUNT.toString(), requestOptions);
Assert.assertEquals(customFields.size(), 5);
Assert.assertEquals(customFields.getPaginationCurrentOffset(), 0);
Assert.assertEquals(customFields.getPaginationTotalNbRecords(), 5);
Assert.assertEquals(customFields.getPaginationMaxNbRecords(), 5);
+
+ final CustomFields allAccountCustomFields = killBillClient.getAllAccountCustomFields(account.getAccountId(), null, AuditLevel.FULL, requestOptions);
+ Assert.assertEquals(allAccountCustomFields.size(), 5);
+
+ final CustomFields allBundleCustomFieldsForAccount = killBillClient.getAllAccountCustomFields(account.getAccountId(), ObjectType.ACCOUNT.name(), AuditLevel.FULL, requestOptions);
+ Assert.assertEquals(allBundleCustomFieldsForAccount.size(), 5);
}
private void doSearchCustomField(final String searchKey, @Nullable final CustomField expectedCustomField) throws KillBillClientException {
- final CustomFields customFields = killBillClient.searchCustomFields(searchKey);
+ final CustomFields customFields = killBillClient.searchCustomFields(searchKey, requestOptions);
if (expectedCustomField == null) {
Assert.assertTrue(customFields.isEmpty());
Assert.assertEquals(customFields.getPaginationCurrentOffset(), 0);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java
index 44a3f3d..5ba7ba8 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java
@@ -79,9 +79,6 @@ public class TestTag extends TestJaxrsBase {
List<TagDefinition> objFromJson = killBillClient.getTagDefinitions(requestOptions);
final int sizeSystemTag = objFromJson.isEmpty() ? 0 : objFromJson.size();
- for (final TagDefinition cur : objFromJson) {
- Assert.assertFalse(SystemTags.isSystemTag(cur.getId()));
- }
final TagDefinition inputBlue = new TagDefinition(null, false, "blue", "relaxing color", ImmutableList.<ObjectType>of());
killBillClient.createTagDefinition(inputBlue, requestOptions);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestLoggingFilterObfuscator.java b/profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestLoggingFilterObfuscator.java
new file mode 100644
index 0000000..76351fd
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestLoggingFilterObfuscator.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.log.obfuscators;
+
+import org.killbill.billing.server.log.ServerTestSuiteNoDB;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+
+public class TestLoggingFilterObfuscator extends ServerTestSuiteNoDB {
+
+ private final LoggingFilterObfuscator obfuscator = new LoggingFilterObfuscator();
+
+ @Test(groups = "fast")
+ public void testAuthorization() throws Exception {
+ verify("2017-08-26T10:28:21,959+0000 lvl='INFO', log='LoggingFilter', th='qtp1071550332-34', xff='', rId='70394abe-7ab6-4b7c-aaf5-17abfcdb9622', aRId='', tRId='', 1 * Server in-bound request\n" +
+ "1 > GET http://127.0.0.1:8080/1.0/kb/security/permissions\n" +
+ "1 > User-Agent: killbill/1.9.0; jruby 9.1.12.0 (2.3.3) 2017-06-15 33c6439 Java HotSpot(TM) 64-Bit Server VM 25.121-b13 on 1.8.0_121-b13 +jit [darwin-x86_64]\n" +
+ "1 > Authorization: Basic YWRtaW46cGFzc3dvcmQ=\n" +
+ "1 > Host: 127.0.0.1:8080\n" +
+ "1 > Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\n" +
+ "1 > Accept: application/json\n" +
+ "1 >",
+ "2017-08-26T10:28:21,959+0000 lvl='INFO', log='LoggingFilter', th='qtp1071550332-34', xff='', rId='70394abe-7ab6-4b7c-aaf5-17abfcdb9622', aRId='', tRId='', 1 * Server in-bound request\n" +
+ "1 > GET http://127.0.0.1:8080/1.0/kb/security/permissions\n" +
+ "1 > User-Agent: killbill/1.9.0; jruby 9.1.12.0 (2.3.3) 2017-06-15 33c6439 Java HotSpot(TM) 64-Bit Server VM 25.121-b13 on 1.8.0_121-b13 +jit [darwin-x86_64]\n" +
+ "1 > Authorization: **************************\n" +
+ "1 > Host: 127.0.0.1:8080\n" +
+ "1 > Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\n" +
+ "1 > Accept: application/json\n" +
+ "1 >");
+ }
+
+ @Test(groups = "fast")
+ public void testApiSecret() throws Exception {
+ verify("2017-08-25T15:28:34,331+0000 lvl='INFO', log='LoggingFilter', th='qtp288887829-1845', xff='', rId='59c40009-ea68-4d87-9580-fe95e9a82c23', aRId='', tRId='11', 3896 * Server in-bound request\n" +
+ "3896 > GET http://127.0.0.1:8080/1.0/kb/paymentMethods/069a4daa-e752-486c-8e40-c9c4f9a732c4?withPluginInfo=true\n" +
+ "3896 > Cookie: JSESSIONID=64faafa1-da74-4ac7-afc7-947cc9871fe5\n" +
+ "3896 > X-Killbill-Apikey: bob\n" +
+ "3896 > Accept: application/json\n" +
+ "3896 > X-Request-Id: 59c40009-ea68-4d87-9580-fe95e9a82c23\n" +
+ "3896 > X-Killbill-Apisecret: lazar\n" +
+ "3896 > User-Agent: killbill/1.9.0; ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin16]\n" +
+ "3896 > Host: 127.0.0.1:8080\n" +
+ "3896 > Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\n" +
+ "3896 >",
+ "2017-08-25T15:28:34,331+0000 lvl='INFO', log='LoggingFilter', th='qtp288887829-1845', xff='', rId='59c40009-ea68-4d87-9580-fe95e9a82c23', aRId='', tRId='11', 3896 * Server in-bound request\n" +
+ "3896 > GET http://127.0.0.1:8080/1.0/kb/paymentMethods/069a4daa-e752-486c-8e40-c9c4f9a732c4?withPluginInfo=true\n" +
+ "3896 > Cookie: JSESSIONID=64faafa1-da74-4ac7-afc7-947cc9871fe5\n" +
+ "3896 > X-Killbill-Apikey: bob\n" +
+ "3896 > Accept: application/json\n" +
+ "3896 > X-Request-Id: 59c40009-ea68-4d87-9580-fe95e9a82c23\n" +
+ "3896 > X-Killbill-Apisecret: *****\n" +
+ "3896 > User-Agent: killbill/1.9.0; ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin16]\n" +
+ "3896 > Host: 127.0.0.1:8080\n" +
+ "3896 > Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\n" +
+ "3896 >");
+ }
+
+ private void verify(final String input, final String output) {
+ final String obfuscated = obfuscator.obfuscate(input, Mockito.mock(ILoggingEvent.class));
+ Assert.assertEquals(obfuscated, output, obfuscated);
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/config/definition/SecurityConfig.java b/util/src/main/java/org/killbill/billing/util/config/definition/SecurityConfig.java
index cfd6a84..f0e5c99 100644
--- a/util/src/main/java/org/killbill/billing/util/config/definition/SecurityConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/definition/SecurityConfig.java
@@ -41,6 +41,11 @@ public interface SecurityConfig extends KillbillConfig {
@Description("LDAP server's User DN format (e.g. uid={0},ou=users,dc=mycompany,dc=com)")
public String getShiroLDAPUserDnTemplate();
+ @Config("org.killbill.security.ldap.dnSearchTemplate")
+ @DefaultNull
+ @Description("LDAP server's DN search template (e.g. sAMAccountName={0}) for search-then-bind authentication (in case a static DN format template isn't enough)")
+ public String getShiroLDAPDnSearchTemplate();
+
@Config("org.killbill.security.ldap.searchBase")
@DefaultNull
@Description("LDAP search base to use")
@@ -87,4 +92,28 @@ public interface SecurityConfig extends KillbillConfig {
@Default("false")
@Description("Whether to ignore SSL certificates checks")
public boolean disableShiroLDAPSSLCheck();
+
+ @Config("org.killbill.security.ldap.followReferrals")
+ @Default("false")
+ @Description("Whether to follow referrals")
+ public boolean followShiroLDAPReferrals();
+
+ // Okta realm
+
+ @Config("org.killbill.security.okta.url")
+ @DefaultNull
+ @Description("Okta org full url")
+ public String getShiroOktaUrl();
+
+ @Config("org.killbill.security.okta.apiToken")
+ @DefaultNull
+ @Description("Okta API token")
+ public String getShiroOktaAPIToken();
+
+ @Config("org.killbill.security.okta.permissionsByGroup")
+ @Default("admin = *:*\n" +
+ "finance = invoice:*, payment:*\n" +
+ "support = entitlement:*, invoice:item_adjust")
+ @Description("Okta permissions by Okta group")
+ public String getShiroOktaPermissionsByGroup();
}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
index bd9ceb2..c103789 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
@@ -28,6 +28,7 @@ import org.killbill.billing.util.config.definition.RbacConfig;
import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
+import org.killbill.billing.util.security.shiro.realm.KillBillOktaRealm;
import org.skife.config.ConfigSource;
import org.skife.config.ConfigurationObjectFactory;
@@ -38,6 +39,7 @@ import com.google.inject.binder.AnnotatedBindingBuilder;
public class KillBillShiroModule extends ShiroModule {
public static final String KILLBILL_LDAP_PROPERTY = "killbill.server.ldap";
+ public static final String KILLBILL_OKTA_PROPERTY = "killbill.server.okta";
public static final String KILLBILL_RBAC_PROPERTY = "killbill.server.rbac";
@@ -45,6 +47,10 @@ public class KillBillShiroModule extends ShiroModule {
return Boolean.parseBoolean(System.getProperty(KILLBILL_LDAP_PROPERTY, "false"));
}
+ public static boolean isOktaEnabled() {
+ return Boolean.parseBoolean(System.getProperty(KILLBILL_OKTA_PROPERTY, "false"));
+ }
+
public static boolean isRBACEnabled() {
return Boolean.parseBoolean(System.getProperty(KILLBILL_RBAC_PROPERTY, "true"));
}
@@ -81,6 +87,12 @@ public class KillBillShiroModule extends ShiroModule {
}
}
+ protected void configureOktaRealm() {
+ if (isOktaEnabled()) {
+ bindRealm().to(KillBillOktaRealm.class).asEagerSingleton();
+ }
+ }
+
@Override
protected void bindSecurityManager(final AnnotatedBindingBuilder<? super SecurityManager> bind) {
super.bindSecurityManager(bind);
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java
index 7779670..ac15666 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java
@@ -39,16 +39,17 @@ import org.apache.shiro.realm.ldap.JndiLdapRealm;
import org.apache.shiro.realm.ldap.LdapContextFactory;
import org.apache.shiro.realm.ldap.LdapUtils;
import org.apache.shiro.subject.PrincipalCollection;
+import org.killbill.billing.util.config.definition.SecurityConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.killbill.billing.util.config.definition.SecurityConfig;
-
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
+import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
+import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
@@ -74,6 +75,7 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
private final String groupSearchFilter;
private final String groupNameId;
private final Map<String, Collection<String>> permissionsByGroup = Maps.newLinkedHashMap();
+ private final String dnSearchFilter;
@Inject
public KillBillJndiLdapRealm(final SecurityConfig securityConfig) {
@@ -87,6 +89,7 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
if (securityConfig.disableShiroLDAPSSLCheck()) {
contextFactory.getEnvironment().put("java.naming.ldap.factory.socket", SkipSSLCheckSocketFactory.class.getName());
}
+ contextFactory.getEnvironment().put("java.naming.referral", securityConfig.followShiroLDAPReferrals() ? "follow" : "ignore");
if (securityConfig.getShiroLDAPUrl() != null) {
contextFactory.setUrl(securityConfig.getShiroLDAPUrl());
}
@@ -101,6 +104,8 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
}
setContextFactory(contextFactory);
+ dnSearchFilter = securityConfig.getShiroLDAPDnSearchTemplate();
+
searchBase = securityConfig.getShiroLDAPSearchBase();
groupSearchFilter = securityConfig.getShiroLDAPGroupSearchFilter();
groupNameId = securityConfig.getShiroLDAPGroupNameID();
@@ -110,8 +115,10 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
// When passing properties on the command line, \n can be escaped
ini.load(securityConfig.getShiroLDAPPermissionsByGroup().replace("\\n", "\n"));
for (final Section section : ini.getSections()) {
- for (final String role : section.keySet()) {
- final Collection<String> permissions = ImmutableList.<String>copyOf(SPLITTER.split(section.get(role)));
+ for (final String rawRole : section.keySet()) {
+ // Un-escape manually = (required if the role name is a DN)
+ final Collection<String> permissions = ImmutableList.<String>copyOf(SPLITTER.split(section.get(rawRole)));
+ final String role = rawRole.replace("\\=", "=");
permissionsByGroup.put(role, permissions);
}
}
@@ -119,6 +126,35 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
}
@Override
+ protected String getUserDn(final String principal) throws IllegalArgumentException, IllegalStateException {
+ if (dnSearchFilter != null) {
+ return findUserDN(principal, getContextFactory());
+ } else {
+ // Use template
+ return super.getUserDn(principal);
+ }
+ }
+
+ private String findUserDN(final String userName, final LdapContextFactory ldapContextFactory) {
+ LdapContext systemLdapCtx = null;
+ try {
+ systemLdapCtx = ldapContextFactory.getSystemLdapContext();
+ final NamingEnumeration<SearchResult> usersFound = systemLdapCtx.search(searchBase,
+ dnSearchFilter.replace(USERDN_SUBSTITUTION_TOKEN, userName),
+ SUBTREE_SCOPE);
+ return usersFound.hasMore() ? usersFound.next().getNameInNamespace() : null;
+ } catch (final AuthenticationException ex) {
+ log.info("LDAP authentication exception='{}'", ex.getLocalizedMessage());
+ throw new IllegalArgumentException(ex);
+ } catch (final NamingException e) {
+ log.info("LDAP exception='{}'", e.getLocalizedMessage());
+ throw new IllegalArgumentException(e);
+ } finally {
+ LdapUtils.closeContext(systemLdapCtx);
+ }
+ }
+
+ @Override
protected AuthorizationInfo queryForAuthorizationInfo(final PrincipalCollection principals, final LdapContextFactory ldapContextFactory) throws NamingException {
final Set<String> userGroups = findLDAPGroupsForUser(principals, ldapContextFactory);
@@ -136,7 +172,7 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
try {
systemLdapCtx = ldapContextFactory.getSystemLdapContext();
return findLDAPGroupsForUser(username, systemLdapCtx);
- } catch (AuthenticationException ex) {
+ } catch (final AuthenticationException ex) {
log.info("LDAP authentication exception='{}'", ex.getLocalizedMessage());
return ImmutableSet.<String>of();
} finally {
@@ -149,21 +185,20 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
groupSearchFilter.replace(USERDN_SUBSTITUTION_TOKEN, userName),
SUBTREE_SCOPE);
+ if (!foundGroups.hasMoreElements()) {
+ return ImmutableSet.<String>of();
+ }
+
+ // There should really only be one entry
+ final SearchResult result = foundGroups.next();
+
// Extract the name of all the groups
- final Iterator<SearchResult> groupsIterator = Iterators.<SearchResult>forEnumeration(foundGroups);
- final Iterator<String> groupsNameIterator = Iterators.<SearchResult, String>transform(groupsIterator,
- new Function<SearchResult, String>() {
- @Override
- public String apply(final SearchResult groupEntry) {
- return extractGroupNameFromSearchResult(groupEntry);
- }
- });
- final Iterator<String> finalGroupsNameIterator = Iterators.<String>filter(groupsNameIterator, Predicates.notNull());
-
- return Sets.newHashSet(finalGroupsNameIterator);
+ final Collection<String> finalGroupsNames = Collections2.<String>filter(extractGroupNamesFromSearchResult(result), Predicates.notNull());
+
+ return Sets.newHashSet(finalGroupsNames);
}
- private String extractGroupNameFromSearchResult(final SearchResult searchResult) {
+ private Collection<String> extractGroupNamesFromSearchResult(final SearchResult searchResult) {
// Get all attributes for that group
final Iterator<? extends Attribute> attributesIterator = Iterators.forEnumeration(searchResult.getAttributes().getAll());
@@ -178,31 +213,23 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
// Extract the group name from the attribute
// Note: at this point, groupNameAttributesIterator should really contain a single element
- final Iterator<String> groupNamesIterator = Iterators.transform(groupNameAttributesIterator,
- new Function<Attribute, String>() {
+ final Iterator<Iterator<?>> groupNamesIterator = Iterators.transform(groupNameAttributesIterator,
+ new Function<Attribute, Iterator<?>>() {
@Override
- public String apply(final Attribute groupNameAttribute) {
+ public Iterator<?> apply(final Attribute groupNameAttribute) {
try {
final NamingEnumeration<?> enumeration = groupNameAttribute.getAll();
- if (enumeration.hasMore()) {
- return enumeration.next().toString();
- } else {
- return null;
- }
- } catch (NamingException namingException) {
- log.warn("Unable to read group name", namingException);
+ return Iterators.forEnumeration(enumeration);
+ } catch (final NamingException namingException) {
+ log.warn("Unable to read group name(s)", namingException);
return null;
}
}
});
- final Iterator<String> finalGroupNamesIterator = Iterators.<String>filter(groupNamesIterator, Predicates.notNull());
- if (finalGroupNamesIterator.hasNext()) {
- return finalGroupNamesIterator.next();
- } else {
- log.warn("Unable to find an attribute matching {}", groupNameId);
- return null;
- }
+ final Iterator<?> finalGroupNamesIterator = Iterators.filter(Iterators.concat(groupNamesIterator), Predicates.notNull());
+
+ return ImmutableList.<String>copyOf(Iterators.transform(finalGroupNamesIterator, Functions.toStringFunction()));
}
private Set<String> groupsPermissions(final Set<String> groups) {
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillOktaRealm.java b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillOktaRealm.java
new file mode 100644
index 0000000..8d75bab
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillOktaRealm.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.security.shiro.realm;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.Ini.Section;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.killbill.billing.util.config.definition.SecurityConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.http.client.AsyncCompletionHandler;
+import com.ning.http.client.AsyncHttpClient;
+import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder;
+import com.ning.http.client.AsyncHttpClientConfig;
+import com.ning.http.client.ListenableFuture;
+import com.ning.http.client.Response;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+
+public class KillBillOktaRealm extends AuthorizingRealm {
+
+ private static final Logger log = LoggerFactory.getLogger(KillBillOktaRealm.class);
+ private static final ObjectMapper mapper = new ObjectMapper();
+ private static final int DEFAULT_TIMEOUT_SECS = 15;
+ private static final Splitter SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
+
+ private final Map<String, Collection<String>> permissionsByGroup = Maps.newLinkedHashMap();
+
+ private final SecurityConfig securityConfig;
+ private final AsyncHttpClient httpClient;
+
+ @Inject
+ public KillBillOktaRealm(final SecurityConfig securityConfig) {
+ this.securityConfig = securityConfig;
+ this.httpClient = new AsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(DEFAULT_TIMEOUT_SECS * 1000).build());
+
+ if (securityConfig.getShiroOktaPermissionsByGroup() != null) {
+ final Ini ini = new Ini();
+ // When passing properties on the command line, \n can be escaped
+ ini.load(securityConfig.getShiroOktaPermissionsByGroup().replace("\\n", "\n"));
+ for (final Section section : ini.getSections()) {
+ for (final String role : section.keySet()) {
+ final Collection<String> permissions = ImmutableList.<String>copyOf(SPLITTER.split(section.get(role)));
+ permissionsByGroup.put(role, permissions);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
+ final String username = (String) getAvailablePrincipal(principals);
+ final String userId = findOktaUserId(username);
+ final Set<String> userGroups = findOktaGroupsForUser(userId);
+
+ final SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(userGroups);
+ final Set<String> stringPermissions = groupsPermissions(userGroups);
+ simpleAuthorizationInfo.setStringPermissions(stringPermissions);
+
+ return simpleAuthorizationInfo;
+ }
+
+ @Override
+ protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token) throws AuthenticationException {
+ final UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+ if (doAuthenticate(upToken)) {
+ // Credentials are valid
+ return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName());
+ } else {
+ throw new AuthenticationException("Okta authentication failed");
+ }
+ }
+
+ private boolean doAuthenticate(final UsernamePasswordToken upToken) {
+ final BoundRequestBuilder builder = httpClient.preparePost(securityConfig.getShiroOktaUrl() + "/api/v1/authn");
+ try {
+ final ImmutableMap<String, String> body = ImmutableMap.<String, String>of("username", upToken.getUsername(),
+ "password", String.valueOf(upToken.getPassword()));
+ builder.setBody(mapper.writeValueAsString(body));
+ } catch (final JsonProcessingException e) {
+ log.warn("Error while generating Okta payload");
+ throw new AuthenticationException(e);
+ }
+ builder.addHeader("Authorization", "SSWS " + securityConfig.getShiroOktaAPIToken());
+ builder.addHeader("Content-Type", "application/json; charset=UTF-8");
+ final Response response;
+ try {
+ final ListenableFuture<Response> futureStatus =
+ builder.execute(new AsyncCompletionHandler<Response>() {
+ @Override
+ public Response onCompleted(final Response response) throws Exception {
+ return response;
+ }
+ });
+ response = futureStatus.get(DEFAULT_TIMEOUT_SECS, TimeUnit.SECONDS);
+ } catch (final TimeoutException toe) {
+ log.warn("Timeout while connecting to Okta");
+ throw new AuthenticationException(toe);
+ } catch (final Exception e) {
+ log.warn("Error while connecting to Okta");
+ throw new AuthenticationException(e);
+ }
+
+ return isAuthenticated(response);
+ }
+
+ private boolean isAuthenticated(final Response oktaRawResponse) {
+ try {
+ final Map oktaResponse = mapper.readValue(oktaRawResponse.getResponseBodyAsStream(), Map.class);
+ if ("SUCCESS".equals(oktaResponse.get("status"))) {
+ return true;
+ } else {
+ log.warn("Okta authentication failed: " + oktaResponse);
+ return false;
+ }
+ } catch (final IOException e) {
+ log.warn("Unable to read response from Okta");
+ throw new AuthenticationException(e);
+ }
+ }
+
+ private String findOktaUserId(final String login) {
+ final String path;
+ try {
+ path = "/api/v1/users/" + URLEncoder.encode(login, "UTF-8");
+ } catch (final UnsupportedEncodingException e) {
+ // Should never happen
+ throw new IllegalStateException(e);
+ }
+
+ final Response oktaRawResponse = doGetRequest(path);
+ try {
+ final Map oktaResponse = mapper.readValue(oktaRawResponse.getResponseBodyAsStream(), Map.class);
+ return (String) oktaResponse.get("id");
+ } catch (final IOException e) {
+ log.warn("Unable to read response from Okta");
+ throw new AuthorizationException(e);
+ }
+ }
+
+ private Set<String> findOktaGroupsForUser(final String userId) {
+ final String path = "/api/v1/users/" + userId + "/groups";
+ final Response response = doGetRequest(path);
+ return getGroups(response);
+ }
+
+ private Response doGetRequest(final String path) {
+ final BoundRequestBuilder builder = httpClient.prepareGet(securityConfig.getShiroOktaUrl() + path);
+ builder.addHeader("Authorization", "SSWS " + securityConfig.getShiroOktaAPIToken());
+ builder.addHeader("Content-Type", "application/json; charset=UTF-8");
+ final Response response;
+ try {
+ final ListenableFuture<Response> futureStatus =
+ builder.execute(new AsyncCompletionHandler<Response>() {
+ @Override
+ public Response onCompleted(final Response response) throws Exception {
+ return response;
+ }
+ });
+ response = futureStatus.get(DEFAULT_TIMEOUT_SECS, TimeUnit.SECONDS);
+ } catch (final TimeoutException toe) {
+ log.warn("Timeout while connecting to Okta");
+ throw new AuthorizationException(toe);
+ } catch (final Exception e) {
+ log.warn("Error while connecting to Okta");
+ throw new AuthorizationException(e);
+ }
+ return response;
+ }
+
+ private Set<String> getGroups(final Response oktaRawResponse) {
+ try {
+ final List<Map> oktaResponse = mapper.readValue(oktaRawResponse.getResponseBodyAsStream(), new TypeReference<List<Map>>() {});
+ final Set<String> groups = new HashSet<String>();
+ for (final Map group : oktaResponse) {
+ final Object groupProfile = group.get("profile");
+ if (groupProfile != null && groupProfile instanceof Map) {
+ groups.add((String) ((Map) groupProfile).get("name"));
+ }
+ }
+ return groups;
+ } catch (final IOException e) {
+ log.warn("Unable to read response from Okta");
+ throw new AuthorizationException(e);
+ }
+ }
+
+ private Set<String> groupsPermissions(final Iterable<String> groups) {
+ final Set<String> permissions = new HashSet<String>();
+ for (final String group : groups) {
+ final Collection<String> permissionsForGroup = permissionsByGroup.get(group);
+ if (permissionsForGroup != null) {
+ permissions.addAll(permissionsForGroup);
+ }
+ }
+ return permissions;
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/tag/api/DefaultTagUserApi.java b/util/src/main/java/org/killbill/billing/util/tag/api/DefaultTagUserApi.java
index b461cc2..55314d1 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/api/DefaultTagUserApi.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/api/DefaultTagUserApi.java
@@ -75,7 +75,7 @@ public class DefaultTagUserApi implements TagUserApi {
@Override
public List<TagDefinition> getTagDefinitions(final TenantContext context) {
- return ImmutableList.<TagDefinition>copyOf(Collections2.transform(tagDefinitionDao.getTagDefinitions(false, internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(context)),
+ return ImmutableList.<TagDefinition>copyOf(Collections2.transform(tagDefinitionDao.getTagDefinitions(true, internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(context)),
new Function<TagDefinitionModelDao, TagDefinition>() {
@Override
public TagDefinition apply(final TagDefinitionModelDao input) {
diff --git a/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillOktaRealm.java b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillOktaRealm.java
new file mode 100644
index 0000000..edc374a
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillOktaRealm.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.security.shiro.realm;
+
+import java.util.Properties;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.killbill.billing.util.UtilTestSuiteNoDB;
+import org.killbill.billing.util.config.definition.SecurityConfig;
+import org.skife.config.ConfigSource;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.config.SimplePropertyConfigSource;
+import org.testng.annotations.Test;
+
+public class TestKillBillOktaRealm extends UtilTestSuiteNoDB {
+
+ @Test(groups = "external", enabled = false)
+ public void testCheckOktaConnection() throws Exception {
+ // Convenience method to verify your Okta connectivity
+ final Properties props = new Properties();
+ props.setProperty("org.killbill.security.okta.url", "https://dev-XXXXXX.oktapreview.com");
+ props.setProperty("org.killbill.security.okta.apiToken", "YYYYYY");
+ props.setProperty("org.killbill.security.okta.permissionsByGroup", "support-group: entitlement:*\n" +
+ "finance-group: invoice:*, payment:*\n" +
+ "ops-group: *:*");
+ final ConfigSource customConfigSource = new SimplePropertyConfigSource(props);
+ final SecurityConfig securityConfig = new ConfigurationObjectFactory(customConfigSource).build(SecurityConfig.class);
+ final KillBillOktaRealm oktaRealm = new KillBillOktaRealm(securityConfig);
+
+ final String username = "pierre";
+ final String password = "password";
+
+ // Check authentication
+ final UsernamePasswordToken token = new UsernamePasswordToken(username, password);
+ final AuthenticationInfo authenticationInfo = oktaRealm.getAuthenticationInfo(token);
+ System.out.println(authenticationInfo);
+
+ // Check permissions
+ final SimplePrincipalCollection principals = new SimplePrincipalCollection(username, username);
+ final AuthorizationInfo authorizationInfo = oktaRealm.doGetAuthorizationInfo(principals);
+ System.out.println("Roles: " + authorizationInfo.getRoles());
+ System.out.println("Permissions: " + authorizationInfo.getStringPermissions());
+ }
+}