Skip to content
Snippets Groups Projects
Verified Commit ec6662b6 authored by Dominik Frantisek Bucik's avatar Dominik Frantisek Bucik
Browse files

feat: :guitar: Pass in ACRs onlyAllowed and blocked IdPs

Lists are read from client entry int DB. Also need to set
`proxy.blocked_idps_enabled=true` and/or
`proxy.only_allowed_idps_enabled=true` in the main config file to enable
passing these alists to proxyIdP

BREAKING CHANGE: requires database update (see v18.0.0.sql script)
parent 702c58ce
1 merge request!387LS AAI template, passing IdP filters via ACRs
Pipeline #412957 passed
This commit is part of merge request !387. Comments created here will be created in the context of that merge request.
Showing
with 204 additions and 17 deletions
...@@ -302,3 +302,13 @@ CREATE TABLE IF NOT EXISTS device_code_request_parameter ( ...@@ -302,3 +302,13 @@ CREATE TABLE IF NOT EXISTS device_code_request_parameter (
param VARCHAR(2048), param VARCHAR(2048),
val VARCHAR(2048) val VARCHAR(2048)
); );
CREATE TABLE IF NOT EXISTS client_only_allowed_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
CREATE TABLE IF NOT EXISTS client_blocked_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
CREATE TABLE IF NOT EXISTS client_only_allowed_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
CREATE TABLE IF NOT EXISTS client_blocked_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
alter table client_only_allowed_idps
add constraint client_only_allowed_idps_client_details_id_fk
foreign key (owner_id) references client_details (id)
on update cascade on delete cascade;
alter table client_blocked_idps
add constraint client_blocked_idps_client_details_id_fk
foreign key (owner_id) references client_details (id)
on update cascade on delete cascade;
\ No newline at end of file
...@@ -205,6 +205,16 @@ CREATE TABLE IF NOT EXISTS client_claims_redirect_uri ( ...@@ -205,6 +205,16 @@ CREATE TABLE IF NOT EXISTS client_claims_redirect_uri (
redirect_uri VARCHAR(2048) redirect_uri VARCHAR(2048)
); );
CREATE TABLE IF NOT EXISTS client_only_allowed_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
CREATE TABLE IF NOT EXISTS client_blocked_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
CREATE TABLE IF NOT EXISTS refresh_token ( CREATE TABLE IF NOT EXISTS refresh_token (
id BIGINT AUTO_INCREMENT PRIMARY KEY, id BIGINT AUTO_INCREMENT PRIMARY KEY,
token_value VARCHAR(4096), token_value VARCHAR(4096),
...@@ -471,3 +481,13 @@ alter table whitelisted_site_scope ...@@ -471,3 +481,13 @@ alter table whitelisted_site_scope
add constraint whitelisted_site_scope_whitelisted_site_id_fk add constraint whitelisted_site_scope_whitelisted_site_id_fk
foreign key (owner_id) references whitelisted_site (id) foreign key (owner_id) references whitelisted_site (id)
on update cascade on delete cascade; on update cascade on delete cascade;
alter table client_only_allowed_idps
add constraint client_only_allowed_idps_client_details_id_fk
foreign key (owner_id) references client_details (id)
on update cascade on delete cascade;
alter table client_blocked_idps
add constraint client_blocked_idps_client_details_id_fk
foreign key (owner_id) references client_details (id)
on update cascade on delete cascade;
\ No newline at end of file
CREATE TABLE IF NOT EXISTS client_only_allowed_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
CREATE TABLE IF NOT EXISTS client_blocked_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
alter table client_only_allowed_idps
add constraint client_only_allowed_idps_client_details_id_fk
foreign key (owner_id) references client_details (id)
on update cascade on delete cascade;
alter table client_blocked_idps
add constraint client_blocked_idps_client_details_id_fk
foreign key (owner_id) references client_details (id)
on update cascade on delete cascade;
\ No newline at end of file
...@@ -209,6 +209,16 @@ CREATE TABLE IF NOT EXISTS client_claims_redirect_uri ( ...@@ -209,6 +209,16 @@ CREATE TABLE IF NOT EXISTS client_claims_redirect_uri (
redirect_uri VARCHAR(2048) redirect_uri VARCHAR(2048)
); );
CREATE TABLE IF NOT EXISTS client_only_allowed_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
CREATE TABLE IF NOT EXISTS client_blocked_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
CREATE TABLE IF NOT EXISTS refresh_token ( CREATE TABLE IF NOT EXISTS refresh_token (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
token_value VARCHAR(4096), token_value VARCHAR(4096),
...@@ -435,6 +445,16 @@ alter table client_scope ...@@ -435,6 +445,16 @@ alter table client_scope
foreign key (owner_id) references client_details (id) foreign key (owner_id) references client_details (id)
on update cascade on delete cascade; on update cascade on delete cascade;
alter table client_only_allowed_idps
add constraint client_only_allowed_idps_client_details_id_fk
foreign key (owner_id) references client_details (id)
on update cascade on delete cascade;
alter table client_blocked_idps
add constraint client_blocked_idps_client_details_id_fk
foreign key (owner_id) references client_details (id)
on update cascade on delete cascade;
alter table device_code alter table device_code
add constraint device_code_client_details_id_fk add constraint device_code_client_details_id_fk
foreign key (client_id) references client_details (client_id) foreign key (client_id) references client_details (client_id)
......
CREATE TABLE IF NOT EXISTS client_only_allowed_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
CREATE TABLE IF NOT EXISTS client_blocked_idps (
owner_id BIGINT,
idp_entity_id VARCHAR(512)
);
alter table client_only_allowed_idps
add constraint client_only_allowed_idps_client_details_id_fk
foreign key (owner_id) references client_details (id)
on update cascade on delete cascade;
alter table client_blocked_idps
add constraint client_blocked_idps_client_details_id_fk
foreign key (owner_id) references client_details (id)
on update cascade on delete cascade;
\ No newline at end of file
...@@ -98,6 +98,8 @@ ...@@ -98,6 +98,8 @@
<prop key="proxy.extSource.name"/> <prop key="proxy.extSource.name"/>
<prop key="proxy.base.url"/> <prop key="proxy.base.url"/>
<prop key="proxy.add_client_id_to_acrs">false</prop> <prop key="proxy.add_client_id_to_acrs">false</prop>
<prop key="proxy.only_allowed_idps_enabled">false</prop>
<prop key="proxy.blocked_idps_enabled">false</prop>
<!-- OIDC STUFF --> <!-- OIDC STUFF -->
<prop key="jwk">file:///etc/perun/perun-oidc-keystore.jwks</prop> <prop key="jwk">file:///etc/perun/perun-oidc-keystore.jwks</prop>
<prop key="id_token.scopes">openid,profile,email,phone,address</prop> <prop key="id_token.scopes">openid,profile,email,phone,address</prop>
...@@ -128,6 +130,7 @@ ...@@ -128,6 +130,7 @@
<prop key="filter.stats.spIdColumnName">spId</prop> <prop key="filter.stats.spIdColumnName">spId</prop>
<prop key="sentry.config.location"/> <prop key="sentry.config.location"/>
<prop key="ga4gh.tokenExchange.brokerUrl"/> <prop key="ga4gh.tokenExchange.brokerUrl"/>
</props> </props>
</property> </property>
</bean> </bean>
...@@ -476,6 +479,8 @@ ...@@ -476,6 +479,8 @@
<property name="krbTokenExchangeRequiredScopes" value="#{'${token-exchange.kerberos.requiredScopes}'.split('\s*,\s*')}"/> <property name="krbTokenExchangeRequiredScopes" value="#{'${token-exchange.kerberos.requiredScopes}'.split('\s*,\s*')}"/>
<property name="requesterIdPrefix" value="${saml.requester-id.prefix}"/> <property name="requesterIdPrefix" value="${saml.requester-id.prefix}"/>
<property name="logRequestsEnabled" value="${logRequestsEnabled}"/> <property name="logRequestsEnabled" value="${logRequestsEnabled}"/>
<property name="onlyAllowedIdpsEnabled" value="${proxy.only_allowed_idps_enabled}"/>
<property name="blockedIdpsEnabled" value="${proxy.blocked_idps_enabled}"/>
</bean> </bean>
<bean id="facilityAttrsConfig" class="cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig"> <bean id="facilityAttrsConfig" class="cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig">
......
...@@ -338,6 +338,18 @@ public class ClientDetailsEntity implements ClientDetails { ...@@ -338,6 +338,18 @@ public class ClientDetailsEntity implements ClientDetails {
@Column(name = "parent_client_id") @Column(name = "parent_client_id")
private Long parentClientId; private Long parentClientId;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "client_only_allowed_idps", joinColumns = @JoinColumn(name = "owner_id"))
@Column(name = "idp_entity_id")
@CascadeOnDelete
private Set<String> onlyAllowedIdps;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "client_blocked_idps", joinColumns = @JoinColumn(name = "owner_id"))
@Column(name = "idp_entity_id")
@CascadeOnDelete
private Set<String> blockedIdps;
@Transient @Transient
private Map<String, Object> additionalInformation = new HashMap<>(); private Map<String, Object> additionalInformation = new HashMap<>();
......
package cz.muni.ics.oidc.saml; package cz.muni.ics.oidc.saml;
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
import cz.muni.ics.oauth2.model.DeviceCode; import cz.muni.ics.oauth2.model.DeviceCode;
import cz.muni.ics.oauth2.repository.impl.DeviceCodeRepository; import cz.muni.ics.oauth2.repository.impl.DeviceCodeRepository;
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
import cz.muni.ics.oidc.models.Facility; import cz.muni.ics.oidc.models.Facility;
import cz.muni.ics.oidc.models.PerunAttributeValue; import cz.muni.ics.oidc.models.PerunAttributeValue;
import cz.muni.ics.oidc.server.adapters.PerunAdapter; import cz.muni.ics.oidc.server.adapters.PerunAdapter;
...@@ -39,10 +41,12 @@ import java.util.Set; ...@@ -39,10 +41,12 @@ import java.util.Set;
import static cz.muni.ics.oauth2.web.endpoint.DeviceEndpoint.PATH_DEVICE_AUTHORIZE; import static cz.muni.ics.oauth2.web.endpoint.DeviceEndpoint.PATH_DEVICE_AUTHORIZE;
import static cz.muni.ics.oauth2.web.endpoint.DeviceEndpoint.USER_CODE; import static cz.muni.ics.oauth2.web.endpoint.DeviceEndpoint.USER_CODE;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.AARC_IDP_HINT; import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.AARC_IDP_HINT;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.BLOCKED_IDPS_ACR_PREFIX;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.CLIENT_ID_PREFIX; import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.CLIENT_ID_PREFIX;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.EFILTER_PREFIX; import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.EFILTER_PREFIX;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.FILTER_PREFIX; import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.FILTER_PREFIX;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.IDP_ENTITY_ID_PREFIX; import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.IDP_ENTITY_ID_PREFIX;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.ONLY_ALLOWED_IDPS_ACR_PREFIX;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_CLIENT_ID; import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_CLIENT_ID;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_MAX_AGE; import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_MAX_AGE;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_PROMPT; import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_PROMPT;
...@@ -55,18 +59,21 @@ public class PerunSamlEntryPoint extends SAMLEntryPoint { ...@@ -55,18 +59,21 @@ public class PerunSamlEntryPoint extends SAMLEntryPoint {
private final FacilityAttrsConfig facilityAttrsConfig; private final FacilityAttrsConfig facilityAttrsConfig;
private final SamlProperties samlProperties; private final SamlProperties samlProperties;
private final DeviceCodeRepository deviceCodeRepository; private final DeviceCodeRepository deviceCodeRepository;
private final ClientDetailsEntityService clientDetailsEntityService;
public PerunSamlEntryPoint(PerunAdapter perunAdapter, public PerunSamlEntryPoint(PerunAdapter perunAdapter,
PerunOidcConfig config, PerunOidcConfig config,
FacilityAttrsConfig facilityAttrsConfig, FacilityAttrsConfig facilityAttrsConfig,
SamlProperties samlProperties, SamlProperties samlProperties,
DeviceCodeRepository deviceCodeRepository) DeviceCodeRepository deviceCodeRepository,
{ ClientDetailsEntityService clientDetailsEntityService
) {
this.perunAdapter = perunAdapter; this.perunAdapter = perunAdapter;
this.config = config; this.config = config;
this.facilityAttrsConfig = facilityAttrsConfig; this.facilityAttrsConfig = facilityAttrsConfig;
this.samlProperties = samlProperties; this.samlProperties = samlProperties;
this.deviceCodeRepository = deviceCodeRepository; this.deviceCodeRepository = deviceCodeRepository;
this.clientDetailsEntityService = clientDetailsEntityService;
} }
@Override @Override
...@@ -176,12 +183,12 @@ public class PerunSamlEntryPoint extends SAMLEntryPoint { ...@@ -176,12 +183,12 @@ public class PerunSamlEntryPoint extends SAMLEntryPoint {
} }
private void processPrompt(Map<String, String> requestParameters, WebSSOProfileOptions options) { private void processPrompt(Map<String, String> requestParameters, WebSSOProfileOptions options) {
if (PerunSamlUtils.needsReAuthByPrompt(requestParameters.getOrDefault(PARAM_PROMPT, null))) { String prompt = requestParameters.getOrDefault(PARAM_PROMPT, "");
log.debug("Transformed prompt parameter ({}) to SAML forceAuthn=true", if (PerunSamlUtils.needsReAuthByPrompt(prompt)) {
requestParameters.get(PARAM_PROMPT)); log.debug("Transformed prompt parameter ({}) to SAML forceAuthn=true", prompt);
options.setForceAuthN(true); options.setForceAuthN(true);
} }
if ("none".equalsIgnoreCase(requestParameters.getOrDefault(PARAM_PROMPT, ""))) { if ("none".equalsIgnoreCase(prompt)) {
log.debug("Detected prompt=none, translating to 'isPassive=true' in SAML"); log.debug("Detected prompt=none, translating to 'isPassive=true' in SAML");
options.setPassive(true); options.setPassive(true);
} }
...@@ -203,25 +210,48 @@ public class PerunSamlEntryPoint extends SAMLEntryPoint { ...@@ -203,25 +210,48 @@ public class PerunSamlEntryPoint extends SAMLEntryPoint {
acrs = convertAcrValuesToList(acrValues); acrs = convertAcrValuesToList(acrValues);
} }
if (!hasAcrForcingIdp(acrs)) { String clientId = requestParameters.getOrDefault(AuthProcFilterConstants.PARAM_CLIENT_ID, null);
String clientId = requestParameters.getOrDefault(AuthProcFilterConstants.PARAM_CLIENT_ID, null); if (StringUtils.hasText(clientId)) {
if (clientId != null) { // ADD FILTER AND E-FILTER
if (config.isAskPerunForIdpFiltersEnabled() && !hasAcrForcingIdp(acrs)) {
String idpFilter = extractIdpFilterForRp(clientId); String idpFilter = extractIdpFilterForRp(clientId);
if (idpFilter != null) { if (idpFilter != null) {
log.debug("Added IdP filter as SAML AuthnContextClassRef ({})", idpFilter); log.debug("Added IdP filter as SAML AuthnContextClassRef ({})", idpFilter);
acrs.add(idpFilter); acrs.add(idpFilter);
} }
} }
}
if (StringUtils.hasText(requestParameters.getOrDefault(PARAM_CLIENT_ID, "")) && config.isAddClientIdToAcrs()) { ClientDetailsEntity client = clientDetailsEntityService.loadClientByClientId(clientId);
String clientIdAcr = CLIENT_ID_PREFIX + requestParameters.get(PARAM_CLIENT_ID); if (client != null) {
log.debug("Adding client_id ACR ({}) to list of AuthnContextClassRefs for purposes" + // ADD BLOCKED IdPs
" of displaying service name on the wayf", clientIdAcr); String blockedIdps = getBlockedIdpsAcr(client);
acrs.add(clientIdAcr); log.debug("blockedIdps ({})", blockedIdps);
if (StringUtils.hasText(blockedIdps)) {
String acr = BLOCKED_IDPS_ACR_PREFIX + blockedIdps;
log.debug("Added blockedIdps as SAML AuthnContextClassRef ({})", acr);
acrs.add(acr);
}
// ADD ONLY ALLOWED IdPs
String onlyAllowedIdps = getOnlyAllowedIdpsAcr(client);
log.debug("allowedIdps ({})", onlyAllowedIdps);
if (StringUtils.hasText(onlyAllowedIdps)) {
String acr = ONLY_ALLOWED_IDPS_ACR_PREFIX + onlyAllowedIdps;
log.debug("Added onlyAllowedIdps as SAML AuthnContextClassRef ({})", acr);
acrs.add(acr);
}
}
// ADD CLIENT_ID
if (config.isAddClientIdToAcrs()) {
String clientIdAcr = CLIENT_ID_PREFIX + requestParameters.get(PARAM_CLIENT_ID);
log.debug("Adding client_id ACR ({}) to list of AuthnContextClassRefs for purposes" +
" of displaying service name on the wayf", clientIdAcr);
acrs.add(clientIdAcr);
}
} }
if (acrs.size() > 0) { if (!acrs.isEmpty()) {
processAcrs(acrs); processAcrs(acrs);
options.setAuthnContexts(acrs); options.setAuthnContexts(acrs);
log.debug("Transformed acr_values ({}) to SAML AuthnContextClassRef ({})", log.debug("Transformed acr_values ({}) to SAML AuthnContextClassRef ({})",
...@@ -237,7 +267,7 @@ public class PerunSamlEntryPoint extends SAMLEntryPoint { ...@@ -237,7 +267,7 @@ public class PerunSamlEntryPoint extends SAMLEntryPoint {
} }
String clientId = requestParameters.getOrDefault(PARAM_CLIENT_ID, null); String clientId = requestParameters.getOrDefault(PARAM_CLIENT_ID, null);
if (StringUtils.hasText(clientId)) { if (StringUtils.hasText(clientId)) {
log.debug("Adding ClientID ({}) to SAML RequesterIDs", requestParameters.get(PARAM_CLIENT_ID)); log.debug("Adding ClientID ({}) to SAML RequesterIDs", clientId);
Set<String> requesterIds = options.getRequesterIds(); Set<String> requesterIds = options.getRequesterIds();
if (requesterIds == null) { if (requesterIds == null) {
requesterIds = new HashSet<>(); requesterIds = new HashSet<>();
...@@ -356,4 +386,26 @@ public class PerunSamlEntryPoint extends SAMLEntryPoint { ...@@ -356,4 +386,26 @@ public class PerunSamlEntryPoint extends SAMLEntryPoint {
return result; return result;
} }
private String getOnlyAllowedIdpsAcr(ClientDetailsEntity client) {
String result = null;
if (config.isOnlyAllowedIdpsEnabled()) {
Set<String> idps = client.getOnlyAllowedIdps();
if (idps != null && !idps.isEmpty()) {
result = String.join(";", idps);
}
}
return result;
}
private String getBlockedIdpsAcr(ClientDetailsEntity client) {
String result = null;
if (config.isBlockedIdpsEnabled()) {
Set<String> idps = client.getBlockedIdps();
if (idps != null && !idps.isEmpty()) {
result = String.join(";", idps);
}
}
return result;
}
} }
package cz.muni.ics.oidc.server.configurations; package cz.muni.ics.oidc.server.configurations;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
......
...@@ -86,6 +86,10 @@ public class PerunOidcConfig implements InitializingBean { ...@@ -86,6 +86,10 @@ public class PerunOidcConfig implements InitializingBean {
private Set<String> krbTokenExchangeRequiredScopes; private Set<String> krbTokenExchangeRequiredScopes;
private boolean onlyAllowedIdpsEnabled = false;
private boolean blockedIdpsEnabled = false;
@Autowired @Autowired
private ServletContext servletContext; private ServletContext servletContext;
...@@ -169,6 +173,8 @@ public class PerunOidcConfig implements InitializingBean { ...@@ -169,6 +173,8 @@ public class PerunOidcConfig implements InitializingBean {
log.info("Localization files path: {}", localizationFilesPath); log.info("Localization files path: {}", localizationFilesPath);
log.info("Email contact: {}", emailContact); log.info("Email contact: {}", emailContact);
log.info("Sentry enabled: {}", StringUtils.hasText(sentryConfigFileLocation)); log.info("Sentry enabled: {}", StringUtils.hasText(sentryConfigFileLocation));
log.info("OnlyAllowedIdPs ACR enabled: {}", onlyAllowedIdpsEnabled);
log.info("BlockedIdPs ACR enabled: {}", blockedIdpsEnabled);
log.info("Perun OIDC version: {}", getPerunOIDCVersion()); log.info("Perun OIDC version: {}", getPerunOIDCVersion());
} }
} }
......
...@@ -32,6 +32,9 @@ public interface AuthProcFilterConstants { ...@@ -32,6 +32,9 @@ public interface AuthProcFilterConstants {
String FILTER_PREFIX = "urn:cesnet:proxyidp:filter:"; String FILTER_PREFIX = "urn:cesnet:proxyidp:filter:";
String EFILTER_PREFIX = "urn:cesnet:proxyidp:efilter:"; String EFILTER_PREFIX = "urn:cesnet:proxyidp:efilter:";
String ONLY_ALLOWED_IDPS_ACR_PREFIX = "urn:cesnet:proxyidp:only_allowed_idps:";
String BLOCKED_IDPS_ACR_PREFIX = "urn:cesnet:proxyidp:blocked_idps:";
String SAML_EPUID = "urn:oid:1.3.6.1.4.1.5923.1.1.1.13"; String SAML_EPUID = "urn:oid:1.3.6.1.4.1.5923.1.1.1.13";
String SAML_EPPN = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6"; String SAML_EPPN = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6";
String SAML_EPTID = "urn:oid:1.3.6.1.4.1.5923.1.1.1.10"; String SAML_EPTID = "urn:oid:1.3.6.1.4.1.5923.1.1.1.10";
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment