Skip to content

Commit

Permalink
Merge pull request #400 from HCL-TECH-SOFTWARE/feature/issue-399-SECV…
Browse files Browse the repository at this point in the history
…alidateAccessToken

Adds SECValidateAccessToken binding and DominoOIDCTokenValidation (fixes #399)
  • Loading branch information
jesse-gallagher authored Sep 26, 2023
2 parents 0b5a59f + 44e441c commit ead4ce5
Show file tree
Hide file tree
Showing 9 changed files with 513 additions and 2 deletions.
37 changes: 37 additions & 0 deletions domino-jnx-api/src/main/java/com/hcl/domino/constants/Bsafe.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* ==========================================================================
* Copyright (C) 2019-2022 HCL America, Inc. ( http://www.hcl.com/ )
* All rights reserved.
* ==========================================================================
* Licensed 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 com.hcl.domino.constants;

/**
* Represents constants originally from the {@code bsafe.h} header file.
*
* @author Jesse Gallagher
* @since 1.29.0
*/
public interface Bsafe {
/** @since Notes/Domino 14.0 */
int fJWT_validate_AllowExpired = 0x00000001;
/** @since Notes/Domino 14.0 */
int fJWT_validate_AllowMSWorkarounds = 0x00000002;
/** @since Notes/Domino 14.0 */
int fJWT_validate_UseCustomEmailClaim = 0x00000010;
/** @since Notes/Domino 14.0 */
int fJWT_validate_AllowAlternateAud = 0x00000020;
/** @since Notes/Domino 14.0 */
int fJWT_validate_EnforceAllowedClients = 0x00000040;

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
*/
package com.hcl.domino.misc;

import com.hcl.domino.constants.Bsafe;
import com.hcl.domino.constants.EditOds;
import com.hcl.domino.constants.OleOds;
import com.hcl.domino.constants.QueryOds;
import com.hcl.domino.constants.StdNames;
import com.hcl.domino.constants.VmOds;

public interface NotesConstants extends ViewFormatConstants, StdNames, QueryOds, EditOds, OleOds, VmOds {
public interface NotesConstants extends ViewFormatConstants, StdNames, QueryOds, EditOds, OleOds, VmOds, Bsafe {

public enum AgentCheck {
CheckRights(0),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.hcl.domino.security;

import java.util.Collection;
import java.util.EnumSet;
import java.util.function.Predicate;
import com.hcl.domino.misc.INumberEnum;
import com.hcl.domino.misc.NotesConstants;

/**
* Representation of an OIDC token that can be used to validate
* credentials via a default {@link CredentialValidationTokenHandler}
* on compatible versions of Domino (14 and above).
*
* @since 1.29.0
*/
public class DominoOIDCTokenValidation {
/**
* Flags that can be passed with the token validation request to
* control internal validation behavior.
*/
public enum Flag implements INumberEnum<Integer> {
AllowExpired(NotesConstants.fJWT_validate_AllowExpired),
AllowMSWorkarounds(NotesConstants.fJWT_validate_AllowMSWorkarounds);

private final int value;

private Flag(int value) {
this.value = value;
}

@Override
public long getLongValue() {
return value;
}

@Override
public Integer getValue() {
return value;
}
}

private final String token;
private final String providerUrl;
private final String requiredScope;
private final String aud;
private final Collection<Flag> flags;
private final String customClaimName;
private final Predicate<String> resourceValidator;
private final Predicate<String> clientIdValidator;

/**
* Constructs a new token validation request using the required parameters.
*
* @param token the token to validate, without any leading "Bearer " from a header
* @param providerUrl the expected provider URL, which must match the Base URL field
* from idpcat.nsf
* @param requiredScope scope required to be in the token to validate, such as
* {@code "Domino.user.all"}
* @param aud the expected audience claim, such as {@code "https://www.example.com"}
*/
public DominoOIDCTokenValidation(String token, String providerUrl, String requiredScope, String aud) {
this(token, providerUrl, requiredScope, aud, null, null, null, null);
}

/**
* Constructs a new token validation request using the required parameters as well as
* extended parameters for validation flags and handling of audience and client IDs.
*
* @param token the token to validate, without any leading "Bearer " from a header
* @param providerUrl the expected provider URL, which must match the Base URL field
* from idpcat.nsf
* @param requiredScope scope required to be in the token to validate, such as
* {@code "Domino.user.all"}
* @param aud the expected audience claim, such as {@code "https://www.example.com"}
* @param flags a {@link Collection} of {@link Flag} values to control validation;
* may be {@code null}
* @param customClaimName the name of the expected claim in the JWT containing the
* email address; defaults to {@code "email"} when blank
* @param resourceValidator a callback to validate additional {@code "aud"} claim
* values, called once per audience; may be {@code null}
* @param clientIdValidator a callback to validate the contents of the {@code "azp"}
* claim; may be {@code null}
*/
public DominoOIDCTokenValidation(String token, String providerUrl, String requiredScope, String aud, Collection<Flag> flags, String customClaimName, Predicate<String> resourceValidator, Predicate<String> clientIdValidator) {
if(token == null || token.isEmpty()) {
throw new IllegalArgumentException("token cannot be empty");
}
if(providerUrl == null || providerUrl.isEmpty()) {
throw new IllegalArgumentException("providerUrl cannot be empty");
}
if(requiredScope == null || requiredScope.isEmpty()) {
throw new IllegalArgumentException("requiredScope cannot be empty");
}
if(aud == null || aud.isEmpty()) {
throw new IllegalArgumentException("aud cannot be empty");
}

this.token = token;
this.providerUrl = providerUrl;
this.requiredScope = requiredScope;
this.aud = aud;
this.flags = flags == null ? EnumSet.noneOf(Flag.class) : EnumSet.copyOf(flags);
this.customClaimName = customClaimName;
this.resourceValidator = resourceValidator;
this.clientIdValidator = clientIdValidator;
}

public String getToken() {
return token;
}
public String getProviderUrl() {
return providerUrl;
}
public String getRequiredScope() {
return requiredScope;
}
public String getAud() {
return aud;
}
public Collection<Flag> getFlags() {
return EnumSet.copyOf(flags);
}
public String getCustomClaimName() {
return customClaimName;
}
public Predicate<String> getClientIdValidator() {
return clientIdValidator;
}
public Predicate<String> getResourceValidator() {
return resourceValidator;
}

@Override
public String toString() {
return String.format(
"DominoOIDCTokenValidation [token=%s, providerUrl=%s, requiredScope=%s, aud=%s, flags=%s]",
token, providerUrl, requiredScope, aud, flags
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* ==========================================================================
* Copyright (C) 2019-2022 HCL America, Inc. ( http://www.hcl.com/ )
* All rights reserved.
* ==========================================================================
* Licensed 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 com.hcl.domino.jna.internal.capi;

import com.sun.jna.Callback;
import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.LongByReference;

/**
* C API methods introduced in R14
*
* @since 1.29.0
*/
public interface INotesCAPI1400 extends Library {

short SECValidateAccessToken(
Memory pszAccessToken,
Memory pszProviderURL,
Memory pszRequiredScope,
Memory pszResourceURL,
int dwFlags,
Pointer vpOptionalParams,
int dwMaxEmailSize,
Memory retszEmail,
LongByReference retqwDurationSec
);

interface ResourceCallback extends Callback {
boolean invoke(Pointer pszAudience);
}
interface ClientCallback extends Callback {
boolean invoke(Pointer pAzp);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* ==========================================================================
* Copyright (C) 2019-2022 HCL America, Inc. ( http://www.hcl.com/ )
* All rights reserved.
* ==========================================================================
* Licensed 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 com.hcl.domino.jna.internal.capi;

import java.util.HashMap;
import java.util.Map;

import com.hcl.domino.DominoException;
import com.hcl.domino.commons.util.DominoUtils;
import com.hcl.domino.commons.util.PlatformUtils;
import com.hcl.domino.jna.JNADominoProcess;
import com.hcl.domino.jna.internal.capi.NotesCAPI.FunctionNameAnnotationMapper;
import com.sun.jna.Function;
import com.sun.jna.Library;
import com.sun.jna.Native;

public class NotesCAPI1400 {
private static volatile INotesCAPI1400 m_instance;
private static volatile INotesCAPI1400 m_instanceWithStackLogging;

private static DominoException m_initError;

public static synchronized INotesCAPI1400 get() {
if (m_instance==null && m_initError==null) {
try {
m_instance = createAPI();
}
catch (Exception e) {
m_initError = new DominoException(0, "Error initializing the Domino JNX API", e);
}
}

if (m_initError!=null) {
throw m_initError;
}

JNADominoProcess.checkThreadEnabledForDomino();

boolean useCallstackLogging = DominoUtils.checkBooleanProperty("jnx.callstacklog", "JNX_CALLSTACKLOG"); //$NON-NLS-1$ //$NON-NLS-2$

if (useCallstackLogging) {
if (m_instanceWithStackLogging==null) {
m_instanceWithStackLogging = NotesCAPI.wrapWithCrashStackLogging(INotesCAPI1400.class, m_instance);
}

return m_instanceWithStackLogging;
}
else {
return m_instance;
}
}

private static INotesCAPI1400 createAPI() {
Map<String,Object> libraryOptions = new HashMap<>();
libraryOptions.put(Library.OPTION_CLASSLOADER, NotesCAPI.class.getClassLoader());
libraryOptions.put(Library.OPTION_FUNCTION_MAPPER, new FunctionNameAnnotationMapper());

if (PlatformUtils.isWin32()) {
libraryOptions.put(Library.OPTION_CALLING_CONVENTION, Function.ALT_CONVENTION); // set w32 stdcall convention
}

INotesCAPI1400 api;
if (PlatformUtils.isWindows()) {
api = Native.load("nnotes", INotesCAPI1400.class, libraryOptions); //$NON-NLS-1$
}
else {
api = Native.load("notes", INotesCAPI1400.class, libraryOptions); //$NON-NLS-1$
}
return api;
}

}
Loading

0 comments on commit ead4ce5

Please sign in to comment.