merege code feature/Backend-DSC-MFA branch
This commit is contained in:
+162
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 org.apache.fineract.useradministration.dsc.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
|
||||
/**
|
||||
* One row per DSC mapping. A user may have multiple rows — we filter by {@link CertStatus} to find
|
||||
* active mappings. The cert serial is globally unique (one cert in the world has one serial), so it
|
||||
* doubles as a natural key.
|
||||
*
|
||||
* <p>Mirrors the shape of an externally-reference {@code bank_user_dsc} table from the procurement
|
||||
* project, with Fineract conventions layered on top: FK to {@code m_appuser}, snake_case columns,
|
||||
* proper DATE/TIMESTAMP types, soft-delete + audit columns, and optimistic locking via
|
||||
* {@code version}.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_bank_user_dsc")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class BankUserDsc extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "appuser_id", nullable = false)
|
||||
private AppUser appUser;
|
||||
|
||||
/**
|
||||
* Free-text user identifier retained for parity with the source schema (often the username or
|
||||
* an external system ID). The {@link #appUser} FK is authoritative; this is for display /
|
||||
* cross-system reconciliation only.
|
||||
*/
|
||||
@Column(name = "dsc_user_id", length = 64)
|
||||
private String dscUserId;
|
||||
|
||||
@Column(name = "dsc_serial_no", length = 128, nullable = false, unique = true)
|
||||
private String dscSerialNo;
|
||||
|
||||
@Column(name = "dsc_name", length = 255)
|
||||
private String dscName;
|
||||
|
||||
/** Base64-encoded SubjectPublicKeyInfo. Used at login-factor time to verify the signature. */
|
||||
@Column(name = "dsc_public_key", columnDefinition = "TEXT", nullable = false)
|
||||
private String dscPublicKey;
|
||||
|
||||
@Column(name = "dsc_expiry_date", nullable = false)
|
||||
private LocalDate dscExpiryDate;
|
||||
|
||||
@Column(name = "date_of_submission", nullable = false)
|
||||
private OffsetDateTime dateOfSubmission;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "dsc_type", length = 32, nullable = false)
|
||||
private DscType dscType;
|
||||
|
||||
@Column(name = "common_name", length = 255)
|
||||
private String commonName;
|
||||
|
||||
/** Cert subject DN. */
|
||||
@Column(name = "issued_to", length = 512)
|
||||
private String issuedTo;
|
||||
|
||||
/** Cert issuer DN — useful in the UI ("eMudhra Sub CA for Class 3 ..."). */
|
||||
@Column(name = "issued_by", length = 512)
|
||||
private String issuedBy;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "cert_status", length = 16, nullable = false)
|
||||
private CertStatus certStatus;
|
||||
|
||||
@Column(name = "revoked_on")
|
||||
private OffsetDateTime revokedOn;
|
||||
|
||||
@Column(name = "revoked_by")
|
||||
private Long revokedBy;
|
||||
|
||||
@Column(name = "revoke_reason", length = 255)
|
||||
private String revokeReason;
|
||||
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private boolean active;
|
||||
|
||||
@Column(name = "is_deleted", nullable = false)
|
||||
private boolean deleted;
|
||||
|
||||
@Column(name = "created_by")
|
||||
private Long createdBy;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@Column(name = "modified_by")
|
||||
private Long modifiedBy;
|
||||
|
||||
@Column(name = "modified_on")
|
||||
private OffsetDateTime modifiedOn;
|
||||
|
||||
@Column(name = "deleted_by")
|
||||
private Long deletedBy;
|
||||
|
||||
@Column(name = "deleted_at")
|
||||
private OffsetDateTime deletedAt;
|
||||
|
||||
@Version
|
||||
@Column(name = "version", nullable = false)
|
||||
private Integer version;
|
||||
|
||||
@PrePersist
|
||||
private void prePersist() {
|
||||
final OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
|
||||
if (createdAt == null) {
|
||||
createdAt = now;
|
||||
}
|
||||
if (dateOfSubmission == null) {
|
||||
dateOfSubmission = now;
|
||||
}
|
||||
if (certStatus == null) {
|
||||
certStatus = CertStatus.ACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience flip: returns true if this row is the kind we'd present in the UI / accept as an
|
||||
* MFA factor. Cheaper to call than re-checking the three flags everywhere.
|
||||
*/
|
||||
public boolean isUsable() {
|
||||
return active && !deleted && certStatus == CertStatus.ACTIVE;
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 org.apache.fineract.useradministration.dsc.domain;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface BankUserDscRepository extends JpaRepository<BankUserDsc, Long> {
|
||||
|
||||
/** All non-deleted rows for the user, newest first — admin view + self list. */
|
||||
List<BankUserDsc> findByAppUser_IdAndDeletedFalseOrderByIdDesc(Long userId);
|
||||
|
||||
/** Only rows usable as an MFA factor (active + not deleted + cert_status=ACTIVE). */
|
||||
List<BankUserDsc> findByAppUser_IdAndActiveTrueAndDeletedFalseAndCertStatus(Long userId, CertStatus certStatus);
|
||||
|
||||
/** Lookup by globally-unique cert serial — used at login-verify time. */
|
||||
Optional<BankUserDsc> findByDscSerialNo(String dscSerialNo);
|
||||
|
||||
/** Used at login-verify time to confirm "this user enrolled this serial". */
|
||||
Optional<BankUserDsc> findByAppUser_IdAndDscSerialNo(Long userId, String dscSerialNo);
|
||||
|
||||
/**
|
||||
* Login gate: returns true if the user has at least one usable DSC mapping that's also not
|
||||
* past its notAfter date. The {@code dscExpiryDate >= today} clause prevents 2FA-gating a
|
||||
* user who's enrolled but whose cert silently expired between log-ins — they'd otherwise be
|
||||
* stuck in a verification loop with a cert the helper would sign with but the server would
|
||||
* accept (the verify side filters too, but the gate is what kicks the user into the DSC flow
|
||||
* in the first place).
|
||||
*/
|
||||
boolean existsByAppUser_IdAndActiveTrueAndDeletedFalseAndCertStatusAndDscExpiryDateGreaterThanEqual(
|
||||
Long userId, CertStatus certStatus, LocalDate today);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 org.apache.fineract.useradministration.dsc.domain;
|
||||
|
||||
/**
|
||||
* Lifecycle of a DSC mapping row in m_bank_user_dsc.
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #ACTIVE} — enrolled and usable as an MFA factor.</li>
|
||||
* <li>{@link #REVOKED} — voluntarily removed by user or admin; kept for audit.</li>
|
||||
* <li>{@link #EXPIRED} — cert past notAfter date; flipped by a scheduled job, not on read.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public enum CertStatus {
|
||||
ACTIVE,
|
||||
REVOKED,
|
||||
EXPIRED;
|
||||
|
||||
public static CertStatus fromString(final String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
for (final CertStatus t : values()) {
|
||||
if (t.name().equalsIgnoreCase(s)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 org.apache.fineract.useradministration.dsc.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
|
||||
/**
|
||||
* One-shot challenge nonce for a DSC enroll or login-2FA cycle.
|
||||
*
|
||||
* <p>The {@link #randomNo} is the public handle the JNLP file and the helper callback use; it must
|
||||
* be a sufficiently long random value (UUID v4) to make guessing infeasible. The {@link #challenge}
|
||||
* is the raw bytes the helper actually signs in LOGIN_2FA flow.
|
||||
*
|
||||
* <p>The row is single-use: once {@code status} flips to VERIFIED or FAILED, further callbacks
|
||||
* referencing the same randomNo must be rejected. Cleanup of EXPIRED/FAILED rows is left to a
|
||||
* scheduled job (TBD) — we keep them for audit.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_dsc_challenge")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class DscChallenge extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@Column(name = "random_no", length = 64, nullable = false, unique = true)
|
||||
private String randomNo;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "appuser_id")
|
||||
private AppUser appUser;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "purpose", length = 32, nullable = false)
|
||||
private DscChallengePurpose purpose;
|
||||
|
||||
/** Random 32-byte payload (hex-encoded) the helper signs with the user's DSC private key. */
|
||||
@Column(name = "challenge", length = 128, nullable = false)
|
||||
private String challenge;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", length = 16, nullable = false)
|
||||
private DscChallengeStatus status;
|
||||
|
||||
/** Serial returned by the helper — set in callback; the server verifies it matches the user's enrolled cert. */
|
||||
@Column(name = "cert_serial_returned", length = 128)
|
||||
private String certSerialReturned;
|
||||
|
||||
/** Base64-encoded RSA-SHA256 signature returned by the helper. NULL for ENROLL purpose. */
|
||||
@Column(name = "signature_b64", columnDefinition = "TEXT")
|
||||
private String signatureB64;
|
||||
|
||||
/** FK populated on successful LOGIN_2FA verify — tells us *which* DSC row was used. */
|
||||
@Column(name = "dsc_id")
|
||||
private Long dscId;
|
||||
|
||||
@Column(name = "error_message", length = 512)
|
||||
private String errorMessage;
|
||||
|
||||
@Column(name = "attempts", nullable = false)
|
||||
private int attempts;
|
||||
|
||||
@Column(name = "expires_at", nullable = false)
|
||||
private OffsetDateTime expiresAt;
|
||||
|
||||
@Column(name = "verified_at")
|
||||
private OffsetDateTime verifiedAt;
|
||||
|
||||
/**
|
||||
* TFA access token minted during a successful LOGIN_2FA verify. Held on the row until the
|
||||
* browser claims it via {@code POST /v1/login/dsc/{rn}/claim}; that call clears the field by
|
||||
* stamping {@link #tfaTokenClaimedAt}. Single-use.
|
||||
*/
|
||||
@Column(name = "tfa_token", length = 255)
|
||||
private String tfaToken;
|
||||
|
||||
@Column(name = "tfa_token_claimed_at")
|
||||
private OffsetDateTime tfaTokenClaimedAt;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@Column(name = "ip_address", length = 64)
|
||||
private String ipAddress;
|
||||
|
||||
@Column(name = "user_agent", length = 512)
|
||||
private String userAgent;
|
||||
|
||||
@PrePersist
|
||||
private void prePersist() {
|
||||
if (createdAt == null) {
|
||||
createdAt = OffsetDateTime.now(ZoneOffset.UTC);
|
||||
}
|
||||
if (status == null) {
|
||||
status = DscChallengeStatus.PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
/** True if the row is past its TTL — used by the verify endpoint before any signature math. */
|
||||
public boolean isExpired() {
|
||||
return expiresAt != null && OffsetDateTime.now(ZoneOffset.UTC).isAfter(expiresAt);
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 org.apache.fineract.useradministration.dsc.domain;
|
||||
|
||||
/**
|
||||
* Why a particular {@code m_dsc_challenge} row was created.
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #ENROLL} — helper returns cert metadata only; server upserts m_bank_user_dsc.</li>
|
||||
* <li>{@link #LOGIN_2FA} — helper signs the challenge nonce; server verifies the signature
|
||||
* against the stored public key and grants TWOFACTOR_AUTHENTICATED on success.</li>
|
||||
* <li>{@link #APPROVAL} — same as LOGIN_2FA but bound to a specific business action.
|
||||
* Future use; persistence layer is ready.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public enum DscChallengePurpose {
|
||||
ENROLL,
|
||||
LOGIN_2FA,
|
||||
APPROVAL;
|
||||
|
||||
public static DscChallengePurpose fromString(final String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
for (final DscChallengePurpose t : values()) {
|
||||
if (t.name().equalsIgnoreCase(s)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 org.apache.fineract.useradministration.dsc.domain;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface DscChallengeRepository extends JpaRepository<DscChallenge, Long> {
|
||||
|
||||
/** Single lookup used by every callback / status / verify endpoint. */
|
||||
Optional<DscChallenge> findByRandomNo(String randomNo);
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 org.apache.fineract.useradministration.dsc.domain;
|
||||
|
||||
public enum DscChallengeStatus {
|
||||
PENDING,
|
||||
VERIFIED,
|
||||
FAILED,
|
||||
EXPIRED;
|
||||
|
||||
public static DscChallengeStatus fromString(final String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
for (final DscChallengeStatus t : values()) {
|
||||
if (t.name().equalsIgnoreCase(s)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 org.apache.fineract.useradministration.dsc.domain;
|
||||
|
||||
public enum DscType {
|
||||
SIGNING,
|
||||
ENCRYPTION;
|
||||
|
||||
public static DscType fromString(final String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
for (final DscType t : values()) {
|
||||
if (t.name().equalsIgnoreCase(s)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user