Skip to content
Merged
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
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
*.iml
out/

WSDL
resources/wsdl/
dk/acto/auth/generated/

# Gradle
Expand Down
51 changes: 51 additions & 0 deletions sso/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,49 @@ dependencies {
tasks.register<Download>("downloadWSDLWsiBruger") {
src("https://wsibruger.unilogin.dk/wsibruger-v6/ws?WSDL")
dest("src/main/resources/wsdl/wsibruger_v6.wsdl")
onlyIf { !dest.exists() }
}

tasks.register<Download>("downloadWSDLWsiInst") {
src("https://wsiinst.unilogin.dk/wsiinst-v5/ws?WSDL")
dest("src/main/resources/wsdl/wsiinst_v5.wsdl")
onlyIf { !dest.exists() }
}

tasks.register<Download>("downloadXsdWsCommon") {
src("https://wsibruger.unilogin.dk/wsibruger-v6/ws?xsd=common/ws-common.xsd")
dest("src/main/resources/wsdl/common/ws-common.xsd")
onlyIf { !dest.exists() }
}

tasks.register<Download>("downloadXsdWsiBruger") {
src("https://wsibruger.unilogin.dk/wsibruger-v6/ws?xsd=wsibruger-ws.xsd")
dest("src/main/resources/wsdl/wsibruger-ws.xsd")
onlyIf { !dest.exists() }
}

tasks.register<Download>("downloadXsdWsiInst") {
src("https://wsiinst.unilogin.dk/wsiinst-v5/ws?xsd=wsiinst-ws.xsd")
dest("src/main/resources/wsdl/wsiinst-ws.xsd")
onlyIf { !dest.exists() }
}

tasks.register<Download>("downloadXsdSchemaCommon") {
src("https://wsibruger.unilogin.dk/wsibruger-v6/ws?xsd=common/schema-common.xsd")
dest("src/main/resources/wsdl/common/schema-common.xsd")
onlyIf { !dest.exists() }
}

tasks.register<Download>("downloadXsdWsibrugerSchema") {
src("https://wsibruger.unilogin.dk/wsibruger-v6/ws?xsd=wsibruger-schema.xsd")
dest("src/main/resources/wsdl/wsibruger-schema.xsd")
onlyIf { !dest.exists() }
}

tasks.register<Download>("downloadXsdWsiinstSchema") {
src("https://wsiinst.unilogin.dk/wsiinst-v5/ws?xsd=wsiinst-schema.xsd")
dest("src/main/resources/wsdl/wsiinst-schema.xsd")
onlyIf { !dest.exists() }
}

tasks.register<Wsdl2Java>("genWsiBruger") {
Expand All @@ -108,6 +146,10 @@ tasks.register<Wsdl2Java>("genWsiBruger") {
encoding.set("UTF-8")
}
dependsOn(tasks.getByName("downloadWSDLWsiBruger"))
dependsOn(tasks.getByName("downloadXsdWsCommon"))
dependsOn(tasks.getByName("downloadXsdSchemaCommon"))
dependsOn(tasks.getByName("downloadXsdWsiBruger"))
dependsOn(tasks.getByName("downloadXsdWsibrugerSchema"))
}

tasks.register<Wsdl2Java>("genWsiInst") {
Expand All @@ -118,6 +160,9 @@ tasks.register<Wsdl2Java>("genWsiInst") {
encoding.set("UTF-8")
}
dependsOn(tasks.getByName("downloadWSDLWsiInst"))
dependsOn(tasks.getByName("downloadXsdWsCommon"))
dependsOn(tasks.getByName("downloadXsdWsiInst"))
dependsOn(tasks.getByName("downloadXsdWsiinstSchema"))
Comment on lines +163 to +165

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

genWsiInst is missing a downloadXsdSchemaCommon dependency.

wsiinst-schema.xsd imports common/schema-common.xsd directly, and wsiinst_v5.wsdl transitively needs it via common/ws-common.xsd's include. genWsiBruger correctly depends on downloadXsdSchemaCommon (Line 150) for the equivalent transitive need, but genWsiInst does not. If this task runs standalone before the checked-in XSD is present (e.g. after deleting generated resources), Wsdl2Java code generation would fail on an unresolved common/schema-common.xsd import.

🛠️ Proposed fix
     dependsOn(tasks.getByName("downloadWSDLWsiInst"))
     dependsOn(tasks.getByName("downloadXsdWsCommon"))
+    dependsOn(tasks.getByName("downloadXsdSchemaCommon"))
     dependsOn(tasks.getByName("downloadXsdWsiInst"))
     dependsOn(tasks.getByName("downloadXsdWsiinstSchema"))
 }
📝 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
dependsOn(tasks.getByName("downloadXsdWsCommon"))
dependsOn(tasks.getByName("downloadXsdWsiInst"))
dependsOn(tasks.getByName("downloadXsdWsiinstSchema"))
dependsOn(tasks.getByName("downloadXsdWsCommon"))
dependsOn(tasks.getByName("downloadXsdSchemaCommon"))
dependsOn(tasks.getByName("downloadXsdWsiInst"))
dependsOn(tasks.getByName("downloadXsdWsiinstSchema"))
🤖 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 `@sso/build.gradle.kts` around lines 163 - 165, The genWsiInst task is missing
the downloadXsdSchemaCommon dependency, so its Wsdl2Java inputs can be
incomplete when run standalone. Update the genWsiInst task configuration in
build.gradle.kts to include downloadXsdSchemaCommon alongside the existing
downloadXsdWsCommon, downloadXsdWsiInst, and downloadXsdWsiinstSchema
dependencies. Use genWsiBruger as the reference pattern for how the transitive
schema dependency should be declared.

}

tasks.compileJava {
Expand All @@ -128,4 +173,10 @@ tasks.compileJava {
tasks.processResources {
dependsOn(tasks.getByName("downloadWSDLWsiBruger"))
dependsOn(tasks.getByName("downloadWSDLWsiInst"))
dependsOn(tasks.getByName("downloadXsdWsCommon"))
dependsOn(tasks.getByName("downloadXsdSchemaCommon"))
dependsOn(tasks.getByName("downloadXsdWsiBruger"))
dependsOn(tasks.getByName("downloadXsdWsibrugerSchema"))
dependsOn(tasks.getByName("downloadXsdWsiInst"))
dependsOn(tasks.getByName("downloadXsdWsiinstSchema"))
}
272 changes: 243 additions & 29 deletions sso/src/main/java/dk/acto/fafnir/sso/provider/UniLoginProvider.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dk.acto.fafnir.sso.provider.unilogin;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
Expand All @@ -14,9 +15,11 @@
@JsonIgnoreProperties(ignoreUnknown = true)
public class InstitutionTilknytning {
@JsonProperty("instnr")
@JsonAlias({"inTnr"})
public String inTnr; // Institution number

@JsonProperty("instnavn")
@JsonAlias({"inTnavn"})
public String inTnavn; // Institution name

// Fields for roles, as they might appear in UserInfo response
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package dk.acto.fafnir.sso.provider.unilogin;

import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public final class UniLoginFlowTrace {

public static final String TRACE_UNIID_SESSION_KEY = "trace_uniid";

public enum Branch {
intro_failed,
userinfo_ok,
userinfo_empty,
soap_fallback,
dedup_applied,
error_no_institutions,
direct_jwt,
direct_jwt_no_org,
org_picker,
org_empty_list,
org_render,
org_post,
jwt_built,
jwt_cached,
logout_redirect,
logout_complete_no_token,
logout_complete_cache_miss,
logout_complete_success
}

private UniLoginFlowTrace() {
}

public static void log(String step, Branch branch, String uniid, HttpSession session, Map<String, Object> extra) {
StringBuilder message = new StringBuilder("UniLogin trace: step=")
.append(step)
.append(" branch=")
.append(branch)
.append(" uniid=")
.append(uniid != null ? uniid : "-")
.append(" session=")
.append(sessionId(session));

if (session != null) {
Object aktoerGruppe = session.getAttribute("aktoer_gruppe");
if (aktoerGruppe != null) {
message.append(" aktoer_gruppe=").append(aktoerGruppe);
}
}

if (extra != null) {
for (Map.Entry<String, Object> entry : extra.entrySet()) {
message.append(' ').append(entry.getKey()).append('=').append(entry.getValue());
}
}

log.info(message.toString());
}
Comment on lines +40 to +64

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sanitize/redact identity fields before writing to the trace log.

log() concatenates uniid, aktoer_gruppe, and all extra values directly into a single string with no escaping. Two concerns:

  • Log forging (CWE-117): if any of these externally-derived values (uniid, institution names/ids from UniLogin) contain \r/\n, an attacker-influenced value could inject forged log lines.
  • This helper is now the central logging point wired into essentially every step of the UniLogin flow (per the stack context), so it persistently logs the user identifier (uniid) and aktoer_gruppe at INFO level across many call sites, expanding the surface area for exposing a personal identifier in logs.

Consider stripping/escaping control characters and truncating/pseudonymizing uniid similar to how sessionId()/tokenPrefix() already truncate session ids and tokens.

🔒 Suggested sanitization
     public static void log(String step, Branch branch, String uniid, HttpSession session, Map<String, Object> extra) {
+        String safeUniid = sanitize(uniid);
         StringBuilder message = new StringBuilder("UniLogin trace: step=")
             .append(step)
             .append(" branch=")
             .append(branch)
             .append(" uniid=")
-            .append(uniid != null ? uniid : "-")
+            .append(safeUniid)
             .append(" session=")
             .append(sessionId(session));

         if (session != null) {
             Object aktoerGruppe = session.getAttribute("aktoer_gruppe");
             if (aktoerGruppe != null) {
-                message.append(" aktoer_gruppe=").append(aktoerGruppe);
+                message.append(" aktoer_gruppe=").append(sanitize(aktoerGruppe.toString()));
             }
         }

         if (extra != null) {
             for (Map.Entry<String, Object> entry : extra.entrySet()) {
-                message.append(' ').append(entry.getKey()).append('=').append(entry.getValue());
+                message.append(' ').append(entry.getKey()).append('=')
+                    .append(sanitize(String.valueOf(entry.getValue())));
             }
         }

         log.info(message.toString());
     }
+
+    private static String sanitize(String value) {
+        if (value == null) {
+            return "-";
+        }
+        return value.replaceAll("[\\r\\n]", "_");
+    }
📝 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
public static void log(String step, Branch branch, String uniid, HttpSession session, Map<String, Object> extra) {
StringBuilder message = new StringBuilder("UniLogin trace: step=")
.append(step)
.append(" branch=")
.append(branch)
.append(" uniid=")
.append(uniid != null ? uniid : "-")
.append(" session=")
.append(sessionId(session));
if (session != null) {
Object aktoerGruppe = session.getAttribute("aktoer_gruppe");
if (aktoerGruppe != null) {
message.append(" aktoer_gruppe=").append(aktoerGruppe);
}
}
if (extra != null) {
for (Map.Entry<String, Object> entry : extra.entrySet()) {
message.append(' ').append(entry.getKey()).append('=').append(entry.getValue());
}
}
log.info(message.toString());
}
public static void log(String step, Branch branch, String uniid, HttpSession session, Map<String, Object> extra) {
String safeUniid = sanitize(uniid);
StringBuilder message = new StringBuilder("UniLogin trace: step=")
.append(step)
.append(" branch=")
.append(branch)
.append(" uniid=")
.append(safeUniid)
.append(" session=")
.append(sessionId(session));
if (session != null) {
Object aktoerGruppe = session.getAttribute("aktoer_gruppe");
if (aktoerGruppe != null) {
message.append(" aktoer_gruppe=").append(sanitize(aktoerGruppe.toString()));
}
}
if (extra != null) {
for (Map.Entry<String, Object> entry : extra.entrySet()) {
message.append(' ').append(entry.getKey()).append('=')
.append(sanitize(String.valueOf(entry.getValue())));
}
}
log.info(message.toString());
}
private static String sanitize(String value) {
if (value == null) {
return "-";
}
return value.replaceAll("[\\r\\n]", "_");
}
🤖 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
`@sso/src/main/java/dk/acto/fafnir/sso/provider/unilogin/UniLoginFlowTrace.java`
around lines 40 - 64, Sanitize the UniLogin trace output in
UniLoginFlowTrace.log before emitting it to log.info. The current message
builder writes uniid, aktoer_gruppe, and all extra map entries directly, so
update log() to escape or strip control characters and apply
redaction/truncation for identity-like fields, using the existing
sessionId()/tokenPrefix() style as a guide. Make sure every value appended in
UniLoginFlowTrace.log is normalized before concatenation so externally derived
data cannot forge log lines or expose raw identifiers.


public static String sessionId(HttpSession session) {
if (session == null || session.getId() == null) {
return "-";
}
String id = session.getId();
return id.length() <= 8 ? id : id.substring(0, 8);
}

public static boolean hasUserInfoInSession(HttpSession session) {
if (session == null) {
return false;
}
UserInfoResponse userInfo = (UserInfoResponse) session.getAttribute("userInfo");
return userInfo != null
&& userInfo.getInstBrugere() != null
&& !userInfo.getInstBrugere().isEmpty();
}

public static List<String> instnrList(List<Institution> institutions) {
if (institutions == null || institutions.isEmpty()) {
return Collections.emptyList();
}
return institutions.stream()
.map(inst -> inst.id)
.filter(id -> id != null && !id.isBlank())
.collect(Collectors.toList());
}

public static String tokenPrefix(String token) {
if (token == null || token.isEmpty()) {
return "-";
}
return token.substring(0, Math.min(8, token.length()));
}

public static void rememberUniid(HttpSession session, String uniid) {
if (session != null && uniid != null) {
session.setAttribute(TRACE_UNIID_SESSION_KEY, uniid);
}
}

public static String uniidFromSession(HttpSession session) {
if (session == null) {
return null;
}
Object value = session.getAttribute(TRACE_UNIID_SESSION_KEY);
return value != null ? value.toString() : null;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dk.acto.fafnir.sso.provider.unilogin;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
Expand All @@ -14,5 +15,6 @@
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserInfoResponse {
@JsonProperty("inst_brugere")
@JsonAlias({"instbrugere", "dk:unilogin:instbrugere"})
public List<InstitutionTilknytning> instBrugere;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import dk.acto.fafnir.api.model.FailureReason;
import dk.acto.fafnir.api.model.conf.FafnirConf;
import dk.acto.fafnir.sso.provider.UniLoginProvider;
import dk.acto.fafnir.sso.provider.unilogin.UniLoginFlowTrace;
import dk.acto.fafnir.sso.provider.unilogin.UniloginTokenCredentials;
import io.vavr.control.Try;
import jakarta.annotation.PostConstruct;
Expand All @@ -20,6 +21,7 @@
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Map;

@Controller
@Slf4j
Expand Down Expand Up @@ -69,11 +71,27 @@ public String getOrg(@RequestParam String user, Model model, HttpServletResponse
}

if (institutionList.isEmpty()) {
if (provider.canIssueJwtWithoutInstitution(session)) {
log.info("No institutions for employee user {}, issuing JWT without org", user);
UniLoginFlowTrace.log("getOrg", UniLoginFlowTrace.Branch.direct_jwt_no_org, user, session, Map.of(
"inst_count", 0,
"session_has_userInfo", UniLoginFlowTrace.hasUserInfoInSession(session)));
AuthenticationResult result = provider.callbackWithoutInstitution(user, session);
response.sendRedirect(result.getUrl(uniloginConf));
return null;
}
log.warn("No institutions found for user: {}, redirecting to failure URL", user);
UniLoginFlowTrace.log("getOrg", UniLoginFlowTrace.Branch.org_empty_list, user, session, Map.of(
"inst_count", 0,
"session_has_userInfo", UniLoginFlowTrace.hasUserInfoInSession(session)));
response.sendRedirect(provider.getFailureUrl(FailureReason.CONNECTION_FAILED));
return null;
} else {
log.info("Adding {} institution(s) to model for template rendering", institutionList.size());
UniLoginFlowTrace.log("getOrg", UniLoginFlowTrace.Branch.org_render, user, session, Map.of(
"inst_count", institutionList.size(),
"session_has_userInfo", UniLoginFlowTrace.hasUserInfoInSession(session),
"instnr_list", UniLoginFlowTrace.instnrList(institutionList)));
model.addAttribute("user", user);
model.addAttribute("institutionList", institutionList);
log.info("Model attributes set - user: {}, institutionList size: {}", user, institutionList.size());
Expand All @@ -86,6 +104,8 @@ public String getOrg(@RequestParam String user, Model model, HttpServletResponse
@ResponseBody
public RedirectView postOrg(@RequestParam String user, @RequestParam String institution,
HttpSession session, HttpServletResponse response) {
UniLoginFlowTrace.log("org_post", UniLoginFlowTrace.Branch.org_post, user, session,
Map.of("institution", institution));
AuthenticationResult result = provider.callbackWithInstitution(user, institution, null, session);
String redirectUrl = result.getUrl(uniloginConf);

Expand All @@ -97,7 +117,8 @@ public RedirectView postOrg(@RequestParam String user, @RequestParam String inst
cookie.setPath("/");
cookie.setMaxAge(300); // 5 minutes
cookie.setHttpOnly(true);
cookie.setSecure(true); // Use secure cookies in production
String fafnirUrl = System.getenv("FAFNIR_URL");
cookie.setSecure(fafnirUrl != null && fafnirUrl.startsWith("https://"));
response.addCookie(cookie);
log.debug("Set logout token cookie as backup");
}
Expand All @@ -117,40 +138,52 @@ public RedirectView postOrg(@RequestParam String user, @RequestParam String inst
@GetMapping("logout-complete")
public RedirectView logoutComplete(HttpSession session, jakarta.servlet.http.HttpServletRequest request) {
log.info("Logout complete callback - retrieving JWT from cache");

String uniid = UniLoginFlowTrace.uniidFromSession(session);

// Try to get token from session first
String token = session != null ? (String) session.getAttribute("logout_token") : null;

String tokenSource = token != null && !token.isEmpty() ? "session" : null;

// Fallback to cookie if session doesn't have it
if ((token == null || token.isEmpty()) && request.getCookies() != null) {
for (jakarta.servlet.http.Cookie cookie : request.getCookies()) {
if ("fafnir_logout_token".equals(cookie.getName())) {
token = cookie.getValue();
tokenSource = "cookie";
log.debug("Retrieved logout token from cookie");
break;
}
}
}

if (token == null || token.isEmpty()) {
log.warn("No logout token found in session or cookie after logout, redirecting to failure");
UniLoginFlowTrace.log("logout_complete", UniLoginFlowTrace.Branch.logout_complete_no_token,
uniid, session, Map.of("token_source", "none"));
return new RedirectView(uniloginConf.getFailureRedirect() + "#" + dk.acto.fafnir.api.model.FailureReason.AUTHENTICATION_FAILED.getErrorCode());
}

// Clear the token from session and cookie
if (session != null) {
session.removeAttribute("logout_token");
}

// Retrieve the JWT from cache using one-time token
String jwt = provider.retrieveJwtFromCache(token);
String jwt = provider.retrieveJwtFromCache(token, uniid, session);
Comment on lines 166 to +172

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Cookie is not cleared despite the comment. The comment states the token is cleared "from session and cookie", but only session.removeAttribute("logout_token") runs — the fafnir_logout_token cookie is never expired and lingers in the browser for its 5‑minute maxAge. Since this handler has no HttpServletResponse to overwrite the cookie, either correct the comment or add a response param to expire it. Note the one-time token is also removed from cache during retrieveJwtFromCache, so any retry with the stale cookie will fail regardless.

✏️ Minimal fix: align the comment with actual behavior
-        // Clear the token from session and cookie
+        // Clear the token from session (the one-time token is also removed from cache below,
+        // so the backup cookie becomes inert even though it is not explicitly expired here)
         if (session != null) {
             session.removeAttribute("logout_token");
         }
📝 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
// Clear the token from session and cookie
if (session != null) {
session.removeAttribute("logout_token");
}
// Retrieve the JWT from cache using one-time token
String jwt = provider.retrieveJwtFromCache(token);
String jwt = provider.retrieveJwtFromCache(token, uniid, session);
// Clear the token from session (the one-time token is also removed from cache below,
// so the backup cookie becomes inert even though it is not explicitly expired here)
if (session != null) {
session.removeAttribute("logout_token");
}
// Retrieve the JWT from cache using one-time token
String jwt = provider.retrieveJwtFromCache(token, uniid, session);
🤖 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
`@sso/src/main/java/dk/acto/fafnir/sso/service/controller/UniLoginController.java`
around lines 166 - 172, The logout handling in UniLoginController does not
actually clear the fafnir_logout_token cookie, only the session attribute
logout_token. Update the comment to match the real behavior, or if you want true
cookie clearing, add an HttpServletResponse parameter and expire the cookie
there before calling provider.retrieveJwtFromCache(token, uniid, session). Keep
the one-time token cache removal behavior unchanged.


if (jwt != null && !jwt.isEmpty()) {
log.debug("JWT retrieved from cache, redirecting to success page");
// Redirect to success page with JWT in hash
log.info("JWT retrieved from cache, redirecting to success page");
UniLoginFlowTrace.log("logout_complete", UniLoginFlowTrace.Branch.logout_complete_success,
uniid, session, Map.of(
"token_source", tokenSource != null ? tokenSource : "unknown",
"cache_token_prefix", UniLoginFlowTrace.tokenPrefix(token)));
return new RedirectView(uniloginConf.getSuccessRedirect() + "#" + jwt);
} else {
log.warn("No valid JWT found in cache after logout (token may be expired or invalid), redirecting to failure");
UniLoginFlowTrace.log("logout_complete", UniLoginFlowTrace.Branch.logout_complete_cache_miss,
uniid, session, Map.of(
"token_source", tokenSource != null ? tokenSource : "unknown",
"cache_token_prefix", UniLoginFlowTrace.tokenPrefix(token)));
return new RedirectView(uniloginConf.getFailureRedirect() + "#" + dk.acto.fafnir.api.model.FailureReason.AUTHENTICATION_FAILED.getErrorCode());
}
}
Expand Down
Loading
Loading