Request for user to enter attribute or actualize phone and email#

The PipeAttrActAdd procedure allows to request the user to enter the attribute value. For cell phone and for email, periodic updating of the contact is implemented. For regular attribute (in the example family_name is used) one-time filling of the attribute. In case the user did not want to fill the attribute, the next request to enter the attribute after a certain time will be realized.

The following modifications must be made to the procedure before use:

  • in the DOMAIN constant, specify the URI at which Blitz Identity Provider is accessible from the user’s browser;

  • in the constants MOBILE_ATTR, EMAIL_ATTR, COMMON_ATTR specify the names of the attributes to be filled in;

  • in the SKIP_TIME_IN_SEC constant specify the time, not more often than which the user will be offered to fill the attribute;

  • in the ACT_TIME_IN_SEC constant specify the time, not more often than which the user will be offered to update phone or email;

  • in the ASK_AT_1ST_LOGIN constant, change the value if the request to fill in the contact should be performed at the first login (usually the first login occurs immediately after the account registration, so the setting is made so that the user is not prompted to fill in the data at the first login);

  • in the body of the procedure instead of _blitz_profile specify the identifier of another application, if the attributes change should be made from an application other than the user profile;

  • set texts in messages for attribute from COMMON_ATTR (default texts for email and phone can also be adjusted) - see :ref:config-pipes-messages. Auxiliary application messages (pipes).

public class PipeAttrActAdd implements Strategy {

    private final Logger logger = LoggerFactory.getLogger("com.identityblitz.idp.flow.dynamic");
    private final static String DOMAIN = "example.com";
    private final static String MOBILE_ATTR = "phone_number";
    private final static String EMAIL_ATTR = "email";
    private final static String COMMON_ATTR = "family_name";
    private final static Integer SKIP_TIME_IN_SEC = 30*86400;
    private final static Integer ACT_TIME_IN_SEC = 30*86400;
    private final static Boolean ASK_AT_1ST_LOGIN = false;

    @Override public StrategyBeginState begin(final Context ctx) {
        if ("login".equals(ctx.prompt())){
            List<String> methods = new ArrayList<String>(Arrays.asList(ctx.availableMethods()));
            methods.remove("cls");
            return StrategyState.MORE(methods.toArray(new String[0]), true);
        } else {
            if(ctx.claims("subjectId") != null)
                return StrategyState.ENOUGH();
            else
                return StrategyState.MORE(new String[]{});
        }
    }

    @Override public StrategyState next(final Context ctx) {
        Instant instant = Instant.now();
        Boolean new_device = false;
        if (ctx.ua().getNewlyCreated() && ctx.justCompletedFactor() == 1 && !ASK_AT_1ST_LOGIN){
            logger.debug("User with sub={} is signing in, pid={}, on a new device",
                ctx.claims("subjectId"), ctx.id());
            new_device = true;
        }
        Integer reqFactor = ctx.user().requiredFactor();
        if(reqFactor == null || reqFactor == ctx.justCompletedFactor()) {
            Enough.Builder en_builder = StrategyState.ENOUGH_BUILDER();
            if (MOBILE_ATTR !=null && !new_device && requireActualizeAttr(MOBILE_ATTR, ctx)) {
                String uri = "https://"+DOMAIN+"/blitz/pipes/attr/act?attr="
                    +MOBILE_ATTR+"&canSkip=true&appId=_blitz_profile&verified=true";
                Set<String> clms = new HashSet<String>(){{
                    add("instanceId");
                    add(MOBILE_ATTR);
                }};
                Set<String> scps = new HashSet<String>(){{
                    add("openid");
                }};
                logger.debug("User has no {} or a non-actualzed {}, so opening pipe",
                    MOBILE_ATTR, MOBILE_ATTR);
                en_builder = en_builder.withPipe(uri, "_blitz_profile", scps, clms);
            } else if (EMAIL_ATTR !=null && !new_device && requireActualizeAttr(EMAIL_ATTR, ctx)) {
                String uri = "https://"+DOMAIN+"/blitz/pipes/attr/act?attr="
                    +EMAIL_ATTR+"&canSkip=true&appId=_blitz_profile&verified=true";
                Set<String> clms = new HashSet<String>(){{
                    add("instanceId");
                    add(EMAIL_ATTR);
                }};
                Set<String> scps = new HashSet<String>(){{
                    add("openid");
                }};
                logger.debug("User has no {} or a non-actualzed {}, so opening pipe",
                    EMAIL_ATTR, EMAIL_ATTR);
                en_builder = en_builder.withPipe(uri, "_blitz_profile", scps, clms);
            } else if (COMMON_ATTR !=null && !new_device &&
                       requireActualizeAttr(COMMON_ATTR, ctx)) {
                String uri = "https://"+DOMAIN+"/blitz/pipes/attr/act?attr="
                    +COMMON_ATTR+"&canSkip=true&appId=_blitz_profile";
                Set<String> clms = new HashSet<String>(){{
                    add("instanceId");
                    add(COMMON_ATTR);
                }};
                Set<String> scps = new HashSet<String>(){{
                    add("openid");
                }};
                logger.debug("User has no {}, so opening pipe", COMMON_ATTR);
                en_builder = en_builder.withPipe(uri, "_blitz_profile", scps, clms);
            }
            return en_builder.build();
        } else {
            return StrategyState.MORE(new String[]{});
        }
    }

    private Boolean requireActualizeAttr(final String attrName, final Context ctx) {
        if (attrName.equals(MOBILE_ATTR) && (ctx.passedTrack().startsWith("1:sms") ||
            ctx.passedTrack().endsWith("sms"))) {
            logger.debug("User subjectId = {}, pid = {} used SMS, so no actualization needed",
                ctx.claims("subjectId"), ctx.id());
            return false;
        }
        if (attrName.equals(EMAIL_ATTR) && ctx.passedTrack().endsWith("email")) {
            logger.debug(
                "User subjectId = {}, pid = {} used EMAIL while auth, so no actualization needed",
                ctx.claims("subjectId"), ctx.id());
            return false;
        }
        Long skpTime = null;
        Long actTime = null;
        long now = Instant.now().getEpochSecond();
        if (ctx.user().userProps().numProp("pipes.act."+attrName+".skippedOn") != null) {
            skpTime = ctx.user().userProps().numProp("pipes.act."+attrName+".skippedOn");
        }
        if (skpTime != null && ((now - skpTime) < SKIP_TIME_IN_SEC)) {
            logger.debug(
                "User subjectId = {}, pid = {} has skipped update '{}' only '{}' seconds ago, no actualization needed", ctx.claims("subjectId"), ctx.id(), attrName, (now - skpTime));
            return false;
        }
        if (ctx.claims(attrName) == null) return true;
        else {
            if (ctx.user().attrsCfmTimes() != null) {
                actTime = ctx.user().attrsCfmTimes().get(attrName);
            }
            if (actTime == null) return true;
            else {
                logger.debug(
                    "User subjectId = {}, pid = {} has updated '{}' '{}' seconds ago, actualization needed = {}", ctx.claims("subjectId"), ctx.id(), attrName, (now - actTime), ((now - actTime) > ACT_TIME_IN_SEC));
                return ((now - actTime) > ACT_TIME_IN_SEC);
            }
        }
    }
}