diff --git a/larky/src/main/java/com/verygood/security/larky/ModuleSupplier.java b/larky/src/main/java/com/verygood/security/larky/ModuleSupplier.java index 40a6806b1..bb532caff 100644 --- a/larky/src/main/java/com/verygood/security/larky/ModuleSupplier.java +++ b/larky/src/main/java/com/verygood/security/larky/ModuleSupplier.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.verygood.security.larky.modules.AccountUpdaterModule; import com.verygood.security.larky.modules.BinasciiModule; import com.verygood.security.larky.modules.C99MathModule; import com.verygood.security.larky.modules.NetworkTokenModule; @@ -98,6 +99,7 @@ public class ModuleSupplier { public static final ImmutableSet VGS_MODULES = ImmutableSet.of( VaultModule.INSTANCE, NetworkTokenModule.INSTANCE, + AccountUpdaterModule.INSTANCE, CerebroModule.INSTANCE, ChaseModule.INSTANCE, JKSModule.INSTANCE diff --git a/larky/src/main/java/com/verygood/security/larky/modules/AccountUpdaterModule.java b/larky/src/main/java/com/verygood/security/larky/modules/AccountUpdaterModule.java new file mode 100644 index 000000000..59eae4d6b --- /dev/null +++ b/larky/src/main/java/com/verygood/security/larky/modules/AccountUpdaterModule.java @@ -0,0 +1,108 @@ +package com.verygood.security.larky.modules; + +import com.google.common.collect.ImmutableList; +import com.verygood.security.larky.modules.vgs.aus.LarkyAccountUpdater; +import com.verygood.security.larky.modules.vgs.aus.MockAccountUpdaterService; +import com.verygood.security.larky.modules.vgs.aus.NoopAccountUpdaterService; +import com.verygood.security.larky.modules.vgs.aus.spi.AccountUpdaterService; +import net.starlark.java.annot.Param; +import net.starlark.java.annot.ParamType; +import net.starlark.java.annot.StarlarkBuiltin; +import net.starlark.java.annot.StarlarkMethod; +import net.starlark.java.eval.*; + +import java.util.List; +import java.util.ServiceLoader; + +@StarlarkBuiltin( + name = "native_au", + category = "BUILTIN", + doc = "Overridable Account Updater API in Larky") +public class AccountUpdaterModule implements LarkyAccountUpdater { + public static final AccountUpdaterModule INSTANCE = new AccountUpdaterModule(); + + public static final String ENABLE_MOCK_PROPERTY = "larky.modules.vgs.nts.enableMockAccountUpdater"; + + private final AccountUpdaterService AccountUpdaterService; + + public AccountUpdaterModule() { + ServiceLoader loader = ServiceLoader.load(AccountUpdaterService.class); + List AccountUpdaterProviders = ImmutableList.copyOf(loader.iterator()); + + if (Boolean.getBoolean(ENABLE_MOCK_PROPERTY)) { + AccountUpdaterService = new MockAccountUpdaterService(); + } else if (AccountUpdaterProviders.isEmpty()) { + AccountUpdaterService = new NoopAccountUpdaterService(); + } else { + if (AccountUpdaterProviders.size() != 1) { + throw new IllegalArgumentException( + String.format( + "AccountUpdaterModule expecting only 1 network token provider of type AccountUpdaterService, found %d", + AccountUpdaterProviders.size())); + } + AccountUpdaterService = AccountUpdaterProviders.get(0); + } + } + + @StarlarkMethod( + name = "get_card", + doc = "Retrieves a newer information for the provided card.", + useStarlarkThread = true, + parameters = { + @Param( + name = "number", + named = true, + doc = "Card PAN. Used to look up the corresponding card information to be returned", + allowedTypes = {@ParamType(type = String.class)}), + @Param( + name = "exp_month", + named = true, + doc = + "Card expiration month. Used to pass to the network for retrieving the corresponding card information", + allowedTypes = {@ParamType(type = String.class)}), + @Param( + name = "exp_year", + named = true, + doc = + "Card expiration year. Used to pass to the network for retrieving the corresponding card information", + allowedTypes = {@ParamType(type = String.class)}), + @Param( + name = "name", + named = true, + doc = + "Card owner name. Used to pass to the network for retrieving the corresponding card information", + allowedTypes = {@ParamType(type = String.class)}), + @Param( + name = "vgs_merchant_id", + named = true, + doc = "VGS merchant id to get a network token for", + defaultValue = "''", + allowedTypes = {@ParamType(type = String.class)}), + }) + @Override + public Dict getCard( + String number, + Integer expireMonth, + Integer expireYear, + String name, + String vgsMerchantId, + StarlarkThread thread) + throws EvalException { + if (number.trim().isEmpty()) { + throw Starlark.errorf("card number argument cannot be blank"); + } + final AccountUpdaterService.Card card; + try { + card = + AccountUpdaterService.getCard(number, expireMonth, expireYear, name, vgsMerchantId); + } catch (UnsupportedOperationException exception) { + throw Starlark.errorf("au.get_card operation must be overridden"); + } + return Dict.builder() + .put("number", card.getNumber()) + .put("expireMonth", StarlarkInt.of(card.getExpireMonth())) + .put("expireYear", StarlarkInt.of(card.getExpireYear())) + .put("name", card.getName()) + .build(thread.mutability()); + } +} diff --git a/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/LarkyAccountUpdater.java b/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/LarkyAccountUpdater.java new file mode 100644 index 000000000..d35d83f3f --- /dev/null +++ b/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/LarkyAccountUpdater.java @@ -0,0 +1,28 @@ +package com.verygood.security.larky.modules.vgs.aus; + +import net.starlark.java.eval.Dict; +import net.starlark.java.eval.EvalException; +import net.starlark.java.eval.StarlarkThread; +import net.starlark.java.eval.StarlarkValue; + +public interface LarkyAccountUpdater extends StarlarkValue { + /** + * Get updated info for the provided card + * + * @param number card's number + * @param expireMonth card's expiration month + * @param expireYear card's expiration year + * @param name the name on the card + * @param vgsMerchantId vgs merchant public identifier + * @param thread Starlark thread object + * @return a dict contains the network token values + */ + Dict getCard( + String number, + Integer expireMonth, + Integer expireYear, + String name, + String vgsMerchantId, + StarlarkThread thread) + throws EvalException; +} diff --git a/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/MockAccountUpdaterService.java b/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/MockAccountUpdaterService.java new file mode 100644 index 000000000..2115e1b07 --- /dev/null +++ b/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/MockAccountUpdaterService.java @@ -0,0 +1,51 @@ +package com.verygood.security.larky.modules.vgs.aus; + +import com.google.common.collect.ImmutableMap; +import com.verygood.security.larky.modules.vgs.aus.spi.AccountUpdaterService; +import org.apache.commons.lang3.StringUtils; + +import java.util.Map; + +public class MockAccountUpdaterService implements AccountUpdaterService { + + private static final String DEFAULT_MERCHANT_ID = "MC8SWErAVLuooPFYz9WTx5W1"; + + private static final Map CARDS = ImmutableMap.of( + "CRD7TPQLA4BXpN3LAYpu3mDSy", Card.builder() + .number("4111111111111111") + .expireMonth(10) + .expireYear(27) + .name("John Doe"), + DEFAULT_MERCHANT_ID, Card.builder() + .number("4242424242424242") + .expireMonth(12) + .expireYear(27) + .name("John Doe") + ); + + @Override + public Card getCard( + String number, Integer expireMonth, Integer expireYear, String name, String vgsMerchantId) { + if (StringUtils.isBlank(vgsMerchantId)) { + return getForDefaultMerchant(); + } + final Card card = + CARDS.get(vgsMerchantId) + .number("5204731600014784") + .expireMonth(12) + .expireYear(24) + .name("John Doe") + .build(); + + return card; + } + + private Card getForDefaultMerchant() { + return CARDS.get(DEFAULT_MERCHANT_ID) + .number("5204731600014784") + .expireMonth(12) + .expireYear(24) + .name("John Doe") + .build(); + } +} diff --git a/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/NoopAccountUpdaterService.java b/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/NoopAccountUpdaterService.java new file mode 100644 index 000000000..3e5c9a165 --- /dev/null +++ b/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/NoopAccountUpdaterService.java @@ -0,0 +1,11 @@ +package com.verygood.security.larky.modules.vgs.aus; + +import com.verygood.security.larky.modules.vgs.aus.spi.AccountUpdaterService; + +public class NoopAccountUpdaterService implements AccountUpdaterService { + @Override + public Card getCard( + String number, Integer expireMonth, Integer expireYear, String name, String vgsMerchantId) { + throw new UnsupportedOperationException("Not implemented"); + } +} diff --git a/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/spi/AccountUpdaterService.java b/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/spi/AccountUpdaterService.java new file mode 100644 index 000000000..a8aa59780 --- /dev/null +++ b/larky/src/main/java/com/verygood/security/larky/modules/vgs/aus/spi/AccountUpdaterService.java @@ -0,0 +1,29 @@ +package com.verygood.security.larky.modules.vgs.aus.spi; + +import lombok.Builder; +import lombok.Data; + + +public interface AccountUpdaterService { + /** + * Get updated info for the provided card + * + * @param number card's number + * @param expireMonth card's expiration month + * @param expireYear card's expiration year + * @param name the name on the card + * @param merchantId vgs merchant public identifier + * @return the updated card + */ + Card getCard( + String number, Integer expireMonth, Integer expireYear, String name, String vgsMerchantId); + + @Data + @Builder + class Card { + private final String number; + private final Integer expireMonth; + private final Integer expireYear; + private final String name; + } +}