Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@
hs_err_pid*

# Ignore everything in this directory
target
target

# Maven versions plugin backup files
*.versionsBackup

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert!

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public enum ActionType {
PRE_UPDATE_PASSWORD,
PRE_UPDATE_PROFILE,
AUTHENTICATION,
PRE_ISSUE_ID_TOKEN;
PRE_ISSUE_ID_TOKEN,
FLOW_EXTENSION;

public String getDisplayName() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,18 @@

package org.wso2.carbon.identity.action.execution.api.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* This class models the User.
Expand All @@ -30,9 +39,11 @@ public class User {

private final String id;
private final List<UserClaim> claims = new ArrayList<>();
private final Map<String, char[]> userCredentials = new HashMap<>();
private final List<String> groups = new ArrayList<>();
private final List<String> roles = new ArrayList<>();
private Organization organization;
private UserStore userStoreDomain;
/**
* Represents the user id when the user is shared across sub-organizations.
* This field differs from the regular user id ({@link #id}) in scenarios where a user is accessed in the context
Expand Down Expand Up @@ -65,15 +76,18 @@ public User(Builder builder) {

this.id = builder.id;
this.claims.addAll(builder.claims);
this.userCredentials.putAll(builder.userCredentials);
this.groups.addAll(builder.groups);
Comment on lines +79 to 80

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Defensively copy char[] credential values to avoid mutable secret leakage.

Collections.unmodifiableMap(...) protects map structure only; the contained char[] values are still mutable and currently shared across builder/instance/caller boundaries.

🔧 Proposed fix
@@
-        this.userCredentials.putAll(builder.userCredentials);
+        builder.userCredentials.forEach((k, v) ->
+                this.userCredentials.put(k, v == null ? null : v.clone()));
@@
     public Map<String, char[]> getUserCredentials() {
-
-        return Collections.unmodifiableMap(userCredentials);
+        Map<String, char[]> copy = new HashMap<>();
+        userCredentials.forEach((k, v) -> copy.put(k, v == null ? null : v.clone()));
+        return Collections.unmodifiableMap(copy);
     }
@@
         public Builder userCredentials(Map<String, char[]> userCredentials) {
-
-            this.userCredentials.putAll(userCredentials);
+            if (userCredentials != null) {
+                userCredentials.forEach((k, v) ->
+                        this.userCredentials.put(k, v == null ? null : v.clone()));
+            }
             return this;
         }

Also applies to: 101-104, 224-227

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/api/model/User.java`
around lines 79 - 80, The User class copies references to mutable credential
char[] values (via this.userCredentials.putAll(builder.userCredentials)) which
allows secret leakage; update all places where userCredentials is copied or
exposed (e.g., the User constructor, any builder.build path, and getters that
return the map) to defensively clone each char[] value: iterate the source map
(builder.userCredentials or internal map), create a copy of each char[] (new
char[length] + System.arraycopy/Arrays.copyOf) and store the clones in the
target map, and likewise when returning credentials from getters return a new
map with cloned char[] values (or immutable wrappers) so no internal char[] is
shared. Ensure you apply this cloning consistently in the User constructor,
builder->User transfer, and any methods referenced around userCredentials and
related fields (e.g., groups handling locations noted in the review).

this.roles.addAll(builder.roles);
this.organization = builder.organization;
this.sharedUserId = builder.sharedUserId;
this.userType = builder.userType;
this.federatedIdP = builder.federatedIdP;
this.accessingOrganization = builder.accessingOrganization;
this.userStoreDomain = builder.userStoreDomain;
}

@JsonInclude(JsonInclude.Include.NON_NULL)
public String getId() {

return id;
Expand All @@ -84,6 +98,11 @@ public List<UserClaim> getClaims() {
return Collections.unmodifiableList(claims);
}

public Map<String, char[]> getUserCredentials() {

return Collections.unmodifiableMap(userCredentials);
}

public List<String> getGroups() {

return Collections.unmodifiableList(groups);
Expand All @@ -99,6 +118,13 @@ public Organization getOrganization() {
return organization;
}

@JsonInclude(JsonInclude.Include.NON_NULL)
Comment thread
KD23243 marked this conversation as resolved.
@JsonSerialize(using = UserStoreNameSerializer.class)
public UserStore getUserStoreDomain() {
Comment thread
KD23243 marked this conversation as resolved.

return userStoreDomain;
}

public String getSharedUserId() {

return sharedUserId;
Expand Down Expand Up @@ -126,9 +152,11 @@ public static class Builder {

private final String id;
private final List<UserClaim> claims = new ArrayList<>();
private final Map<String, char[]> userCredentials = new HashMap<>();
private final List<String> groups = new ArrayList<>();
private final List<String> roles = new ArrayList<>();
private Organization organization;
private UserStore userStoreDomain;
private String sharedUserId;
private String userType;
private String federatedIdP;
Expand Down Expand Up @@ -163,6 +191,12 @@ public Builder organization(Organization organization) {
return this;
}

public Builder userStoreDomain(UserStore userStoreDomain) {

this.userStoreDomain = userStoreDomain;
return this;
}

public Builder sharedUserId(String sharedUserId) {

this.sharedUserId = sharedUserId;
Expand All @@ -187,9 +221,24 @@ public Builder accessingOrganization(Organization accessingOrganization) {
return this;
}

public Builder userCredentials(Map<String, char[]> userCredentials) {

this.userCredentials.putAll(userCredentials);
return this;
}

public User build() {

return new User(this);
}
}

private static class UserStoreNameSerializer extends JsonSerializer<UserStore> {

@Override
public void serialize(UserStore value, JsonGenerator gen, SerializerProvider serializers) throws IOException {

gen.writeString(value.getName() != null ? value.getName() : "");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting an empty string might be problematic for the other action types, as this will start adding this userstoredomain attribute to the user object with an empty value.

Since this userStoreDomain is specific for the Inflow extension, I suggest using an extended User class in the inflow extensions component.

}
}
Comment on lines +236 to +243

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this

}
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,12 @@ public ActionExecutionStatus execute(ActionType actionType, String actionId,
throw new ActionExecutionException("Action Id cannot be blank.");
}

try {
Action action = getActionByActionId(actionType, actionId, tenantDomain);
return execute(action, flowContext, tenantDomain);
} catch (ActionExecutionRuntimeException e) {
LOG.debug("Skip executing action for action type: " + actionType.name(), e);
// Skip executing actions when no action available is considered as action execution being successful.
Action action = getActionByActionId(actionType, actionId, tenantDomain);
if (action == null) {
LOG.debug("No action found for action Id: " + actionId + ". Skipping action execution.");
return new SuccessStatus.Builder().setResponseContext(flowContext.getContextData()).build();
Comment on lines +151 to 154

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard the new concatenated DEBUG log.

The new debug statement does string concatenation without a debug guard.

Proposed fix
         Action action = getActionByActionId(actionType, actionId, tenantDomain);
         if (action == null) {
-            LOG.debug("No action found for action Id: " + actionId + ". Skipping action execution.");
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("No action found for action Id: " + actionId + ". Skipping action execution.");
+            }
             return new SuccessStatus.Builder().setResponseContext(flowContext.getContextData()).build();
         }

As per coding guidelines, "DEBUG: guard all non-trivial debug logs with if (log.isDebugEnabled())."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Action action = getActionByActionId(actionType, actionId, tenantDomain);
if (action == null) {
LOG.debug("No action found for action Id: " + actionId + ". Skipping action execution.");
return new SuccessStatus.Builder().setResponseContext(flowContext.getContextData()).build();
Action action = getActionByActionId(actionType, actionId, tenantDomain);
if (action == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("No action found for action Id: " + actionId + ". Skipping action execution.");
}
return new SuccessStatus.Builder().setResponseContext(flowContext.getContextData()).build();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/internal/service/impl/ActionExecutorServiceImpl.java`
around lines 151 - 154, The new LOG.debug call in the block after calling
getActionByActionId(actionType, actionId, tenantDomain) performs string
concatenation unguarded; wrap that debug log in a conditional using
LOG.isDebugEnabled() to avoid unnecessary work when debug logging is off (i.e.,
check if (LOG.isDebugEnabled()) before calling LOG.debug("No action found for
action Id: " + actionId + ". Skipping action execution.")), keeping the rest of
the method flow (returning new
SuccessStatus.Builder().setResponseContext(flowContext.getContextData()).build())
unchanged.

}
return execute(action, flowContext, tenantDomain);
Comment on lines +151 to +156

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ashanthamara for feedback

}

private ActionExecutionStatus<?> execute(Action action, FlowContext flowContext, String tenantDomain)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ public boolean isExecutionForActionTypeEnabled(ActionType actionType) {
return isActionTypeEnabled(ActionTypeConfig.PRE_UPDATE_PROFILE.getActionTypeEnableProperty());
case PRE_ISSUE_ID_TOKEN:
return isActionTypeEnabled(ActionTypeConfig.PRE_ISSUE_ID_TOKEN.getActionTypeEnableProperty());
case FLOW_EXTENSION:
return isActionTypeEnabled(ActionTypeConfig.FLOW_EXTENSION.getActionTypeEnableProperty());
default:
return false;
}
Expand Down Expand Up @@ -333,6 +335,8 @@ public String getRetiredUpToVersion(ActionType actionType) {
return getVersion(ActionTypeConfig.PRE_UPDATE_PROFILE.getRetiredUpToVersionProperty());
case PRE_ISSUE_ID_TOKEN:
return getVersion(ActionTypeConfig.PRE_ISSUE_ID_TOKEN.getRetiredUpToVersionProperty());
case FLOW_EXTENSION:
return getVersion(ActionTypeConfig.FLOW_EXTENSION.getRetiredUpToVersionProperty());
default:
return null;
}
Expand Down Expand Up @@ -417,7 +421,13 @@ private enum ActionTypeConfig {
"Actions.Types.PreIssueIdToken.ActionRequest.ExcludedParameters.Parameter",
"Actions.Types.PreIssueIdToken.ActionRequest.AllowedHeaders.Header",
"Actions.Types.PreIssueIdToken.ActionRequest.AllowedParameters.Parameter",
"Actions.Types.PreIssueIdToken.Version.RetiredUpTo");
"Actions.Types.PreIssueIdToken.Version.RetiredUpTo"),
FLOW_EXTENSION("Actions.Types.FlowExtension.Enable",
"Actions.Types.FlowExtension.ActionRequest.ExcludedHeaders.Header",
"Actions.Types.FlowExtension.ActionRequest.ExcludedParameters.Parameter",
"Actions.Types.FlowExtension.ActionRequest.AllowedHeaders.Header",
"Actions.Types.FlowExtension.ActionRequest.AllowedParameters.Parameter",
"Actions.Types.FlowExtension.Version.RetiredUpTo");

private final String actionTypeEnableProperty;
private final String excludedHeadersProperty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ public enum ErrorMessage {
"An unsupported rule has been provided for action version %s."),
ERROR_MAXIMUM_ATTRIBUTES_LIMIT_EXCEEDED("60011", "Maximum attributes limit exceeded.",
"The number of configured attributes: %s exceeds the maximum allowed limit: %s"),
ERROR_INVALID_ATTRIBUTES("60012", "Invalid attribute provided.",
"%s"),
ERROR_INVALID_ATTRIBUTES("60012", "Invalid attribute provided.", "%s"),
ERROR_EMPTY_ATTRIBUTE_VALUE("60013", "Invalid attribute provided.",
"Each attribute must be a non-empty string."),
ERROR_UNSUPPORTED_ATTRIBUTE("60014", "Unsupported attribute provided.",
"The attribute %s is not supported to be shared with the extension."),
ERROR_ACTION_NAME_ALREADY_EXISTS("60015", "Action name already exists.",
"An action with the name '%s' already exists for the given action type."),

// Server errors.
ERROR_WHILE_ADDING_ACTION("65001", "Error while adding Action.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ public enum ActionTypes {
"PRE_ISSUE_ID_TOKEN",
"Pre Issue ID Token",
"Configure an extension point for modifying ID token via a custom service.",
Category.PRE_POST);
Category.PRE_POST),
FLOW_EXTENSION(
"flowExtension",
"FLOW_EXTENSION",
"Flow Extension",
"Configure an extension point within any flow via a custom service.",
Category.FLOW_EXTENSION);

private final String pathParam;
private final String actionType;
Expand Down Expand Up @@ -131,7 +137,8 @@ public static ActionTypes[] filterByCategory(Category category) {
*/
public enum Category {
PRE_POST,
IN_FLOW
IN_FLOW,
FLOW_EXTENSION
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public static ActionDTOModelResolver getActionDTOModelResolver(Action.ActionType
return actionDTOModelResolvers.get(Action.ActionTypes.PRE_UPDATE_PASSWORD);
case PRE_ISSUE_ACCESS_TOKEN:
return actionDTOModelResolvers.get(Action.ActionTypes.PRE_ISSUE_ACCESS_TOKEN);
case FLOW_EXTENSION:
return actionDTOModelResolvers.get(Action.ActionTypes.FLOW_EXTENSION);
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,7 @@ private ActionConverterFactory() {

public static ActionConverter getActionConverter(Action.ActionTypes actionType) {

switch (actionType) {
case PRE_UPDATE_PROFILE:
return actionConverters.get(Action.ActionTypes.PRE_UPDATE_PROFILE);
case PRE_UPDATE_PASSWORD:
return actionConverters.get(Action.ActionTypes.PRE_UPDATE_PASSWORD);
case PRE_ISSUE_ACCESS_TOKEN:
default:
return null;
}
return actionConverters.get(actionType);
Comment thread
KD23243 marked this conversation as resolved.
}

public static void registerActionConverter(ActionConverter actionConverter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.wso2.carbon.identity.action.management.api.exception.ActionMgtException;
import org.wso2.carbon.identity.action.management.api.exception.ActionMgtServerException;
import org.wso2.carbon.identity.action.management.api.model.Action;
import org.wso2.carbon.identity.action.management.api.model.Action.ActionTypes;
import org.wso2.carbon.identity.action.management.api.model.ActionDTO;
import org.wso2.carbon.identity.action.management.api.model.Authentication;
import org.wso2.carbon.identity.action.management.api.model.EndpointConfig;
Expand Down Expand Up @@ -76,6 +77,7 @@ public Action addAction(String actionType, Action action, String tenantDomain) t
Action.ActionTypes castedActionType = Action.ActionTypes.valueOf(resolvedActionType);
ActionValidatorFactory.getActionValidator(castedActionType).doPreAddActionValidations(
castedActionType, ActionManagementConfig.getInstance().getLatestVersion(castedActionType), action);
validateActionNameUniqueness(action.getName(), null, castedActionType, tenantId);
// Check whether the maximum allowed actions per type is reached.
validateMaxActionsPerType(resolvedActionType, tenantDomain);
String generatedActionId = UUID.randomUUID().toString();
Expand Down Expand Up @@ -161,6 +163,7 @@ public Action updateAction(String actionType, String actionId, Action action, St
Action.ActionTypes castedActionType = Action.ActionTypes.valueOf(resolvedActionType);
ActionValidatorFactory.getActionValidator(castedActionType).doPreUpdateActionValidations(
castedActionType, resolveActionVersionAtUpdating(action, existingActionDTO), action);
validateActionNameUniqueness(action.getName(), actionId, castedActionType, tenantId);
ActionDTO updatingActionDTO = buildActionDTOForUpdate(resolvedActionType, actionId, action);

Comment thread
KD23243 marked this conversation as resolved.
DAO_FACADE.updateAction(updatingActionDTO, existingActionDTO, tenantId);
Expand Down Expand Up @@ -317,8 +320,10 @@ private String getActionTypeFromPath(String actionType) throws ActionMgtClientEx
*/
private void validateMaxActionsPerType(String actionType, String tenantDomain) throws ActionMgtException {

// In-flow actions are not limited by the maximum actions per action type; eg: AUTHENTICATION action type.
if (Action.ActionTypes.Category.IN_FLOW.equals(Action.ActionTypes.valueOf(actionType).getCategory())) {
// In-flow and extension actions are not limited by the maximum actions per action type.
Action.ActionTypes.Category category = Action.ActionTypes.valueOf(actionType).getCategory();
if (Action.ActionTypes.Category.IN_FLOW.equals(category)
|| Action.ActionTypes.Category.FLOW_EXTENSION.equals(category)) {
return;
}
Map<String, Integer> actionsCountPerType = getActionsCountPerType(tenantDomain);
Expand Down Expand Up @@ -365,8 +370,13 @@ private ActionDTO buildActionDTOForCreation(String actionType, String actionId,
throws ActionMgtServerException {

Action.ActionTypes resolvedActionType = Action.ActionTypes.valueOf(actionType);
Action.Status resolvedStatus = resolvedActionType.getCategory() == Action.ActionTypes.Category.IN_FLOW ?
Action.Status.ACTIVE : Action.Status.INACTIVE;
// Only IN_FLOW and FLOW_EXTENSION category actions (e.g., AUTHENTICATION, FLOW_EXTENSION)
// start ACTIVE and can be used immediately. All other categories (e.g., PRE_POST) start
// INACTIVE and require explicit activation.
Action.ActionTypes.Category category = resolvedActionType.getCategory();
Action.Status resolvedStatus = (category == Action.ActionTypes.Category.IN_FLOW
|| category == Action.ActionTypes.Category.FLOW_EXTENSION)
? Action.Status.ACTIVE : Action.Status.INACTIVE;

String actionVersion = ActionManagementConfig.getInstance().getLatestVersion(resolvedActionType);

Expand Down Expand Up @@ -470,4 +480,21 @@ private Action buildAction(String actionType, ActionDTO actionDTO) {
.rule(actionDTO.getActionRule())
.build();
}

private void validateActionNameUniqueness(String name, String excludeId, ActionTypes actionType, int tenantId)
throws ActionMgtException {

if (name == null || !ActionTypes.FLOW_EXTENSION.equals(actionType)) {
return;
}
List<ActionDTO> existingActions = DAO_FACADE.getActionsByActionType(actionType.getActionType(), tenantId);
boolean duplicateExists = existingActions.stream()
.filter(dto -> excludeId == null || !excludeId.equals(dto.getId()))
.anyMatch(dto -> name.equalsIgnoreCase(dto.getName()));

if (duplicateExists) {
throw ActionManagementExceptionHandler.handleClientException(
ErrorMessage.ERROR_ACTION_NAME_ALREADY_EXISTS, name);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ public String getLatestVersion(ActionTypes actionType) throws ActionMgtServerExc
return getVersion(
ActionTypeConfig.PRE_UPDATE_PROFILE.getLatestVersionProperty(), actionType);
case PRE_ISSUE_ID_TOKEN:
return getVersion(ActionTypeConfig.PRE_ISSUE_ID_TOKEN.getLatestVersionProperty(), actionType);
return getVersion(
ActionTypeConfig.PRE_ISSUE_ID_TOKEN.getLatestVersionProperty(), actionType);
case FLOW_EXTENSION:
return getVersion(
ActionTypeConfig.FLOW_EXTENSION.getLatestVersionProperty(), actionType);
default:
throw new ActionMgtServerException("Unsupported action type: " + actionType);
Comment thread
KD23243 marked this conversation as resolved.
}
Expand Down Expand Up @@ -140,6 +144,11 @@ public enum ActionTypeConfig {
"Actions.Types.PreIssueIdToken.ActionRequest.ExcludedHeaders.Header",
"Actions.Types.PreIssueIdToken.ActionRequest.ExcludedParameters.Parameter",
"Actions.Types.PreIssueIdToken.Version.Latest"
),
FLOW_EXTENSION(
"Actions.Types.FlowExtension.ActionRequest.ExcludedHeaders.Header",
null,
"Actions.Types.FlowExtension.Version.Latest"
);

private final String excludedHeadersProperty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ public Object[][] actionTypesProvider() {
{Action.ActionTypes.PRE_ISSUE_ID_TOKEN, "preIssueIdToken", "PRE_ISSUE_ID_TOKEN",
"Pre Issue ID Token",
"Configure an extension point for modifying ID token via a custom service.",
Action.ActionTypes.Category.PRE_POST}
Action.ActionTypes.Category.PRE_POST},
{Action.ActionTypes.FLOW_EXTENSION, "flowExtension", "FLOW_EXTENSION",
"Flow Extension",
"Configure an extension point within any flow via a custom service.",
Action.ActionTypes.Category.FLOW_EXTENSION}
};
}

Expand All @@ -76,7 +80,9 @@ public Object[][] filterByCategoryProvider() {
new Action.ActionTypes[]{Action.ActionTypes.PRE_ISSUE_ACCESS_TOKEN,
Action.ActionTypes.PRE_UPDATE_PASSWORD, Action.ActionTypes.PRE_UPDATE_PROFILE,
Action.ActionTypes.PRE_REGISTRATION, Action.ActionTypes.PRE_ISSUE_ID_TOKEN}},
{Action.ActionTypes.Category.IN_FLOW, new Action.ActionTypes[]{Action.ActionTypes.AUTHENTICATION}}
{Action.ActionTypes.Category.IN_FLOW, new Action.ActionTypes[]{Action.ActionTypes.AUTHENTICATION}},
{Action.ActionTypes.Category.FLOW_EXTENSION,
new Action.ActionTypes[]{Action.ActionTypes.FLOW_EXTENSION}}
};
}

Expand Down
Loading
Loading