What's new

2025.07.5 release

This section lists all of the changes added in patch 5 of the 2025.07 release.

New Jutro version

Digital reference applications now use Jutro patch version 10.11.4-next-20260424184700. This brings enhanced security to the Jutro Design System.

ProducerEngage gateway account API endpoint changes

Guidewire has modified the configuration of the ProducerEngage /gateway/account API endpoint for improved security. There are steps that you can take to have improved security and need to manually add the changes to your own files.

After making this change, ServiceRepEngage users must ensure that they have the correct producer codes assigned to their account in PolicyCenter in order to create quotes.

The following sections show the original and updated code. Bold lines in each code block indicate where the changes were made for this fix. Make the following changes to /gsrc/edge/capabilities/gateway/account/AccountHandler.gs:

Original file content:

@JsonRpcRunAsInternalGWUser
  @JsonRpcMethod
  @ApidocMethodDescription("Returns or creates and returns the given account.")
  @ApidocAvailableSince("4.0")
  function getOrCreateAccount(anAccountDTO: AccountDTO): AccountDTO {
    if (anAccountDTO.AccountNumber == null) {
      final var newAccount = _accountPlugin.createAccount(anAccountDTO)
      return _accountPlugin.accountBaseDetailsToDTO(newAccount)
    }

    final var account = _accountRetrievalPlugin.getAccountByNumber(anAccountDTO.AccountNumber)

    return _accountPlugin.accountBaseDetailsToDTO(account)
  }

Updated file content:

@JsonRpcRunAsInternalGWUser
  @JsonRpcMethod
  @ApidocMethodDescription("Returns or creates and returns the given account.")
  @ApidocAvailableSince("4.0")
  function getOrCreateAccount(anAccountDTO: AccountDTO): AccountDTO {
    if (anAccountDTO.AccountNumber == null) {
      verifyProducerCodeOwnership(anAccountDTO)
      final var newAccount = _accountPlugin.createAccount(anAccountDTO)
      return _accountPlugin.accountBaseDetailsToDTO(newAccount)
    }

    final var account = _accountRetrievalPlugin.getAccountByNumber(anAccountDTO.AccountNumber)

    return _accountPlugin.accountBaseDetailsToDTO(account)
  }

  private function verifyProducerCodeOwnership(anAccountDTO: AccountDTO) {
    if (anAccountDTO.ProducerCodes == null || !anAccountDTO.ProducerCodes.HasElements) {
      return
    }
    final var ownedCodes = User.util.CurrentUser.UserProducerCodes*.ProducerCode*.Code.toSet()
    final var unauthorized = anAccountDTO.ProducerCodes.firstWhere(\pc -> !ownedCodes.contains(pc.Code))
    if (unauthorized != null) {
      throw new AuthorizationException() {:Message = "User is not authorized to create accounts under producer code: " + unauthorized.Code}
    }
  }

ProducerEngage API search result changes

Guidewire has modified the configuration of the ProducerEngage API search response for improved security. There are steps that you can take to have improved security and need to manually add the changes to your own files.

You must modify the following files:
  • /gsrc/edge/capabilities/gateway/account/AccountHandler.gs‎
  • /gsrc/edge/capabilities/gateway/account/DefaultAccountPlugin.gs‎
  • /gsrc/edge/capabilities/gateway/account/IAccountPlugin.gs‎
You must also create the following files and update them:
  • /gsrc/edge/capabilities/gateway/account/dto/AccountSearchHolderDTO.gs‎
  • /gsrc/edge/capabilities/gateway/account/dto/AccountSearchResultDTO.gs‎

AccountHandler changes

Make the following changes to /gsrc/edge/capabilities/gateway/account/AccountHandler.gs‎:

Original file content:
package edge.capabilities.gateway.account

uses edge.jsonrpc.AbstractRpcHandler
uses edge.capabilities.gateway.account.dto.AccountJobsSummaryDTO
uses edge.aspects.validation.annotations.Context
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.el.Expr
uses edge.di.annotations.InjectableNode
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.capabilities.gateway.account.dto.AccountDTO
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser
uses edge.capabilities.gateway.account.search.dto.AccountSearchCriteriaDTO
uses edge.capabilities.gateway.account.search.IAccountSearchPlugin
uses edge.capabilities.gateway.job.submission.dto.ProductSelectionDTO
uses edge.capabilities.gateway.job.submission.dto.NewSubmissionDTO
uses edge.capabilities.gateway.job.submission.ISubmissionPlugin
uses edge.capabilities.gateway.note.dto.NoteDTO
uses edge.capabilities.gateway.note.INotePlugin
uses edge.capabilities.helpers.GatewayJobUtil
uses edge.capabilities.gateway.document.IDocumentPlugin
uses edge.capabilities.gateway.document.dto.DocumentDTO
uses edge.capabilities.gateway.billing.dto.BillingInvoiceDTO
uses edge.capabilities.gateway.billing.IPolicyPeriodBillingSummaryPlugin
uses edge.capabilities.gateway.billing.dto.PolicyPeriodBillingSummaryDTO
uses java.lang.Exception

uses edge.security.authorization.exception.AuthorizationException
uses edge.security.permission.IPermissionCheckPlugin
uses java.lang.SecurityException
uses edge.capabilities.gateway.claim.IClaimSummaryPlugin
uses edge.capabilities.gateway.claim.dto.ClaimSummaryDTO
uses edge.PlatformSupport.Logger
uses edge.PlatformSupport.Reflection
uses edge.jsonrpc.exception.JsonRpcInternalErrorException
uses edge.exception.EntityNotFoundException
uses edge.capabilities.gateway.account.dto.AccountSummaryDTO
uses edge.capabilities.gateway.account.dto.AccountJobsDTO
uses java.lang.Integer
uses edge.capabilities.gateway.job.dto.JobSummaryDTO
uses edge.capabilities.gateway.job.IJobSummaryPlugin
uses edge.capabilities.gateway.billing.IAccountBillingPlugin
uses edge.capabilities.gateway.billing.dto.AccountBillingDTO
uses edge.capabilities.gateway.account.dto.PolicyTransactionSearchCriteriaDTO
uses edge.capabilities.gateway.account.search.dto.AccountSearchSummaryDTO
uses gw.api.database.Query
uses edge.PlatformSupport.Bundle
uses edge.capabilities.helpers.pagination.dto.QueryOptionsDTO
uses edge.capabilities.helpers.pagination.dto.QueryParameterDTO
uses java.util.ArrayList
@JsonRpcRunAsInternalGWUser
  @JsonRpcMethod
  @ApidocMethodDescription("Returns a list of potentially existing accounts.")
  @ApidocAvailableSince("4.0")
  function getPotentialExistingAccounts(accountSearchCriteriaDTO: AccountSearchCriteriaDTO): AccountDTO[] {
    if(!perm.System.searchaccounts) {
      throw new AuthorizationException(){:Message = "User is not authorized to search accounts"}
    }
    var anAccountSearchCriteria = _accountSearchPlugin.createSearchCriteria(accountSearchCriteriaDTO)

    if (accountSearchCriteriaDTO.ContactType == ContactType.TC_COMPANY) {
      /*If company name is greater than 5 characters then do an exact match*/
      anAccountSearchCriteria.CompanyNameExact = (anAccountSearchCriteria.CompanyName.length < 5)
    } else if (accountSearchCriteriaDTO.ContactType == ContactType.TC_PERSON) {
      anAccountSearchCriteria.LastNameExact = (anAccountSearchCriteria.LastName.length < 5)
      anAccountSearchCriteria.FirstNameExact = (anAccountSearchCriteria.FirstName.length < 5)
    }

    final var potentialAccounts = anAccountSearchCriteria.performSearch()*.Account

    return _accountPlugin.toDTOArray(potentialAccounts)
  }
Updated file content:
package edge.capabilities.gateway.account

uses edge.jsonrpc.AbstractRpcHandler
uses edge.capabilities.gateway.account.dto.AccountJobsSummaryDTO
uses edge.aspects.validation.annotations.Context
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.el.Expr
uses edge.di.annotations.InjectableNode
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.capabilities.gateway.account.dto.AccountDTO
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser
uses edge.capabilities.gateway.account.search.dto.AccountSearchCriteriaDTO
uses edge.capabilities.gateway.account.search.IAccountSearchPlugin
uses edge.capabilities.gateway.job.submission.dto.ProductSelectionDTO
uses edge.capabilities.gateway.job.submission.dto.NewSubmissionDTO
uses edge.capabilities.gateway.job.submission.ISubmissionPlugin
uses edge.capabilities.gateway.note.dto.NoteDTO
uses edge.capabilities.gateway.note.INotePlugin
uses edge.capabilities.helpers.GatewayJobUtil
uses edge.capabilities.gateway.document.IDocumentPlugin
uses edge.capabilities.gateway.document.dto.DocumentDTO
uses edge.capabilities.gateway.billing.dto.BillingInvoiceDTO
uses edge.capabilities.gateway.billing.IPolicyPeriodBillingSummaryPlugin
uses edge.capabilities.gateway.billing.dto.PolicyPeriodBillingSummaryDTO
uses java.lang.Exception

uses edge.security.authorization.exception.AuthorizationException
uses edge.security.permission.IPermissionCheckPlugin
uses java.lang.SecurityException
uses edge.capabilities.gateway.claim.IClaimSummaryPlugin
uses edge.capabilities.gateway.claim.dto.ClaimSummaryDTO
uses edge.PlatformSupport.Logger
uses edge.PlatformSupport.Reflection
uses edge.jsonrpc.exception.JsonRpcInternalErrorException
uses edge.exception.EntityNotFoundException
uses edge.capabilities.gateway.account.dto.AccountSummaryDTO
uses edge.capabilities.gateway.account.dto.AccountJobsDTO
uses java.lang.Integer
uses edge.capabilities.gateway.job.dto.JobSummaryDTO
uses edge.capabilities.gateway.job.IJobSummaryPlugin
uses edge.capabilities.gateway.billing.IAccountBillingPlugin
uses edge.capabilities.gateway.billing.dto.AccountBillingDTO
uses edge.capabilities.gateway.account.dto.PolicyTransactionSearchCriteriaDTO
uses edge.capabilities.gateway.account.search.dto.AccountSearchSummaryDTO
uses edge.capabilities.gateway.account.dto.AccountSearchResultDTO
uses gw.api.database.Query
uses edge.PlatformSupport.Bundle
uses edge.capabilities.helpers.pagination.dto.QueryOptionsDTO
uses edge.capabilities.helpers.pagination.dto.QueryParameterDTO
uses java.util.ArrayList
@JsonRpcRunAsInternalGWUser
  @JsonRpcMethod
  @ApidocMethodDescription("Returns a list of potentially existing accounts.")
  @ApidocAvailableSince("4.0")
  function getPotentialExistingAccounts(accountSearchCriteriaDTO: AccountSearchCriteriaDTO): AccountSearchResultDTO[] {
    if(!perm.System.searchaccounts) {
      throw new AuthorizationException(){:Message = "User is not authorized to search accounts"}
    }
    var anAccountSearchCriteria = _accountSearchPlugin.createSearchCriteria(accountSearchCriteriaDTO)

    if (accountSearchCriteriaDTO.ContactType == ContactType.TC_COMPANY) {
      /*If company name is greater than 5 characters then do an exact match*/
      anAccountSearchCriteria.CompanyNameExact = (anAccountSearchCriteria.CompanyName.length < 5)
    } else if (accountSearchCriteriaDTO.ContactType == ContactType.TC_PERSON) {
      anAccountSearchCriteria.LastNameExact = (anAccountSearchCriteria.LastName.length < 5)
      anAccountSearchCriteria.FirstNameExact = (anAccountSearchCriteria.FirstName.length < 5)
    }

    final var potentialAccounts = anAccountSearchCriteria.performSearch()*.Account

    return _accountPlugin.toSearchResultDTOArray(potentialAccounts)
  }

DefaultAccount plugin changes

Make the following changes to /gsrc/edge/capabilities/gateway/account/DefaultAccountPlugin.gs:

Original file content:
package edge.capabilities.gateway.account

uses edge.PlatformSupport.CurrencyPlatformUtil
uses edge.capabilities.gateway.account.dto.AccountDTO
uses edge.capabilities.gateway.contact.IContactPlugin
uses edge.capabilities.gateway.currency.local.ICurrencyPlugin
uses edge.capabilities.gateway.policy.IPolicySummaryPlugin
uses edge.di.annotations.ForAllGwNodes
uses java.lang.IllegalArgumentException
uses java.util.Date
uses edge.capabilities.gateway.billing.IAccountBillingSummaryPlugin
uses edge.capabilities.gateway.contact.dto.ContactBaseDTO
uses java.lang.Exception
uses edge.PlatformSupport.Reflection
uses edge.PlatformSupport.Logger
uses edge.capabilities.gateway.user.local.IProducerCodePlugin
uses edge.capabilities.helpers.GatewayJobUtil
uses edge.capabilities.gateway.claim.IClaimSummaryPlugin
uses edge.exception.EntityNotFoundException
uses gw.pl.currency.MonetaryAmount
uses gw.product.ProducerCodeSearchCriteria
uses edge.capabilities.policycommon.accountcontact.IAccountContactPlugin
override function toDTOArray(accounts: Account[]): AccountDTO[] {
    if (accounts != null && !accounts.IsEmpty){
      return accounts.map(\acc -> toDTO(acc))
    }
    return new AccountDTO[]{}
  }

override function updateAccount(anAccount: Account, dto: AccountDTO) {
    var accountHolder: ContactBaseDTO = null
    if (dto.AccountHolder != null) {
      _accContactPlugin.updateContact(anAccount.AccountHolderContact, dto.AccountHolder)
    }
  }
Updated file content:
package edge.capabilities.gateway.account

uses edge.PlatformSupport.CurrencyPlatformUtil
uses edge.capabilities.gateway.account.dto.AccountDTO
uses edge.capabilities.gateway.account.dto.AccountSearchResultDTO
uses edge.capabilities.gateway.account.dto.AccountSearchHolderDTO
uses edge.capabilities.address.dto.AddressDTO
uses edge.capabilities.gateway.contact.IContactPlugin
uses edge.capabilities.gateway.currency.local.ICurrencyPlugin
uses edge.capabilities.gateway.policy.IPolicySummaryPlugin
uses edge.di.annotations.ForAllGwNodes
uses java.lang.IllegalArgumentException
uses java.util.Date
uses edge.capabilities.gateway.billing.IAccountBillingSummaryPlugin
uses edge.capabilities.gateway.contact.dto.ContactBaseDTO
uses java.lang.Exception
uses edge.PlatformSupport.Reflection
uses edge.PlatformSupport.Logger
uses edge.capabilities.gateway.user.local.IProducerCodePlugin
uses edge.capabilities.helpers.GatewayJobUtil
uses edge.capabilities.gateway.claim.IClaimSummaryPlugin
uses edge.exception.EntityNotFoundException
uses gw.pl.currency.MonetaryAmount
uses gw.product.ProducerCodeSearchCriteria
uses edge.capabilities.policycommon.accountcontact.IAccountContactPlugin
  override function toDTOArray(accounts: Account[]): AccountDTO[] {
    if (accounts != null && !accounts.IsEmpty){
      return accounts.map(\acc -> toDTO(acc))
    }
    return new AccountDTO[]{}
  }

  override function toSearchResultDTOArray(accounts: Account[]): AccountSearchResultDTO[] {
    if (accounts == null || accounts.IsEmpty) {
      return new AccountSearchResultDTO[]{}
    }
    return accounts.map(\acc -> {
      final var dto = new AccountSearchResultDTO()
      dto.AccountNumber = acc.AccountNumber
      final var holder = new AccountSearchHolderDTO()
      holder.DisplayName = acc.AccountHolderContact?.DisplayName
      final var contactAddress = acc.AccountHolderContact?.PrimaryAddress
      if (contactAddress != null) {
        final var addr = new AddressDTO()
        addr.AddressLine1 = contactAddress.AddressLine1
        addr.City = contactAddress.City
        addr.State = contactAddress.State
        addr.PostalCode = contactAddress.PostalCode
        addr.Country = contactAddress.Country
        holder.PrimaryAddress = addr
      }
      dto.AccountHolder = holder
      return dto
    })
  }

  override function updateAccount(anAccount: Account, dto: AccountDTO) {
    var accountHolder: ContactBaseDTO = null
    if (dto.AccountHolder != null) {
      _accContactPlugin.updateContact(anAccount.AccountHolderContact, dto.AccountHolder)
    }
  }

IAccount plugin changes

Make the following changes to /gsrc/edge/capabilities/gateway/account/IAccountPlugin.gs‎:

Original file content:
package edge.capabilities.gateway.account

uses edge.capabilities.gateway.account.dto.AccountDTO

interface IAccountPlugin {

  public function toDTO(anAccount : Account) : AccountDTO
  public function toDTOArray(accounts : Account[]) : AccountDTO[]
  public function accountBaseDetailsToDTO(anAccount : Account) : AccountDTO
  public function accountBaseDetailsToDTOArray(accounts : Account[]) : AccountDTO[]
  public function updateAccount(anAccount : Account, dto : AccountDTO)
  public function createAccount(dto : AccountDTO) : Account

}
Updated file content:
package edge.capabilities.gateway.account

uses edge.capabilities.gateway.account.dto.AccountDTO
uses edge.capabilities.gateway.account.dto.AccountSearchResultDTO

interface IAccountPlugin {

  public function toDTO(anAccount : Account) : AccountDTO
  public function toDTOArray(accounts : Account[]) : AccountDTO[]
  public function accountBaseDetailsToDTO(anAccount : Account) : AccountDTO
  public function accountBaseDetailsToDTOArray(accounts : Account[]) : AccountDTO[]
  public function toSearchResultDTOArray(accounts : Account[]) : AccountSearchResultDTO[]
  public function updateAccount(anAccount : Account, dto : AccountDTO)
  public function createAccount(dto : AccountDTO) : Account

}

AccountSearchHolderDTO changes

In the /gsrc/edge/capabilities/gateway/account/dto/‎ directory, create a new file called AccountSearchHolderDTO.gs and add the following content:
package edge.capabilities.gateway.account.dto

uses edge.jsonmapper.JsonProperty
uses edge.capabilities.address.dto.AddressDTO

/**
 * Minimal account-holder summary for search results.
 * Contains only display name and primary address — no PII (DOB, licence, email, phone).
 */
class AccountSearchHolderDTO {

  @JsonProperty
  var _displayName : String as DisplayName

  @JsonProperty
  var _primaryAddress : AddressDTO as PrimaryAddress

}

AccountSearchResultDTO changes

In the /gsrc/edge/capabilities/gateway/account/dto/‎ directory, create a new file called AccountSearchResultDTO.gs‎ and add the following content:
package edge.capabilities.gateway.account.dto

uses edge.jsonmapper.JsonProperty

/**
 * Minimal DTO for account search results. Contains only the fields required to identify
 * an account in a list — account number and a stripped-down account holder (display name
 * and primary address only). Sensitive fields (DOB, licence, email, phone, billing data,
 * activity counts) are intentionally excluded.
 */
class AccountSearchResultDTO {

  @JsonProperty
  var _accountNumber : String as AccountNumber

  @JsonProperty
  var _accountHolder : AccountSearchHolderDTO as AccountHolder

}

CustomerEngage Quote and Buy API endpoint changes

Guidewire has modified the configuration of the CustomerEngage Quote and Buy /guidance/guidance API endpoint for improved security. There are steps that you can take to have improved security and need to manually add the changes to your own files.

The following files have been updated:
  • DefaultAccountRetrievalPlugin.gs
  • GuidanceHandler.gs
  • UserProfileHandler.gs
  • AccountUtil.gs
  • EdgeAuthorizationHandler.gs
  • DefaultEnrollmentValidationPlugin.gs

UserProfileHandler changes

Make the following changes to /gsrc/edge/capabilities/profileinfo/user/UserProfileHandler.gs:

Original file content:
@JsonRpcMethod
  @ApidocMethodDescription("When the user wishes to update their account contact information.")
  @ApidocAvailableSince("5.0")
  public function updateAccountContactSummary(newAccountSummaryDTO : AccountSummaryDTO) {
    final var account = AccountUtil.getAccountByAccountNumber(newAccountSummaryDTO.AccountNumber)
    AccountUtil.verifyAccountForEffectiveUser(_userProvider.EffectiveUser, account);

    var accountContact = newAccountSummaryDTO.AccountContact
    _accountPlugin.updateAccountContactSummary(account, accountContact)

    if(newAccountSummaryDTO.isBillingAddressSame) {
      _accountPlugin.updateAccountBillingSummary(account, newAccountSummaryDTO.AccountContact)
    } else {
      _accountPlugin.updateAccountBillingSummary(account, newAccountSummaryDTO.BillingContact)
    }
  }
}
Updated file content:
@JsonRpcMethod
  @ApidocMethodDescription("When the user wishes to update their account contact information.")
  @ApidocAvailableSince("5.0")
  public function updateAccountContactSummary(newAccountSummaryDTO : AccountSummaryDTO) {
    final var account = AccountUtil.getAccountByAccountNumber(newAccountSummaryDTO.AccountNumber, _userProvider.EffectiveUser)

    var accountContact = newAccountSummaryDTO.AccountContact
    _accountPlugin.updateAccountContactSummary(account, accountContact)

    if(newAccountSummaryDTO.isBillingAddressSame) {
      _accountPlugin.updateAccountBillingSummary(account, newAccountSummaryDTO.AccountContact)
    } else {
      _accountPlugin.updateAccountBillingSummary(account, newAccountSummaryDTO.BillingContact)
    }
  }
}

CustomerEngage Account Management improved access control

Guidewire has modified the configuration of the CustomerEngage Account Management access control when creating a Personal Auto quote for improved security in quote submissions. There are steps that you can take to have improved security and need to manually add the changes to your own files. The changes need to be added to the following files:
  • /gsrc/edge/capabilities/quote/submission/GatewayQuoteHandler.gs‎
  • /gsrc/edge/capabilities/quote/helper/session/DefaultSessionPlugin.gs‎
  • /gsrc/edge/capabilities/quote/helper/session/ISessionPlugin.gs‎
  • /gsrc/edge/capabilities/quote/submission/QuoteHandler.gs‎
  • /gsrc/edge/capabilities/quote/submission/UnderwritingQuoteRetrievalHandler.gs‎
  • /gsrc/edge/capabilities/quote/submission/UnderwritingQuoteHandler.gs‎
  • /gsrc/edge/oauth/authplugin/EdgeAuthenticationSourceCreatorPlugin.gs‎
  • /gsrc/main/gsrc/edge/servlet/security/DefaultHttpRequestUserIdentityPlugin.gs‎
You must also update the following front-end files:
  • applications/common/capabilities-react/gw-capability-quoteandbind-ho-react/HOWizard.jsx
  • applications/common/capabilities-react/gw-capability-quoteandbind-pa-react/PAWizard.jsx

GatewayQuoteHandler changes

The following changes need to be made to /gsrc/edge/capabilities/quote/submission/GatewayQuoteHandler.gs‎:

Original file content:
package edge.capabilities.quote.submission

uses edge.capabilities.policycommon.accountcontact.IAccountContactPlugin
uses edge.capabilities.policycommon.validation.dto.PCValidationResultDTOMapper
uses edge.capabilities.policycommon.validation.dto.PCValidationResultsDTO
uses edge.capabilities.policyjob.quoting.exception.UnderwritingException
uses edge.capabilities.quote.submission.quoting.util.SubmissionUnderwritingIssuesUtil
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser
uses edge.di.annotations.InjectableNode
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.el.Expr
uses edge.aspects.validation.annotations.Context
uses edge.security.authorization.IAuthorizerProviderPlugin
uses edge.capabilities.quote.helper.session.ISessionPlugin
uses edge.capabilities.quote.mailing.IQuoteMailingPlugin
uses edge.capabilities.quote.submission.lob.ILobMetadataPlugin
uses edge.capabilities.quote.submission.dto.QuoteDataDTO
uses edge.capabilities.quote.submission.dto.QuoteRetrievalDTO
uses edge.capabilities.policyjob.binding.IBindingPlugin
uses edge.capabilities.quote.submission.quoting.ISubmissionQuotePlugin
uses edge.capabilities.quote.submission.lob.IQuoteLobDataPlugin
uses edge.capabilities.quote.submission.base.IBaseSubmissionPlugin
uses edge.capabilities.quote.submission.lob.LobDTO
uses edge.capabilities.policyjob.lob.dto.LobCoveragesDTO
uses edge.capabilities.policyjob.lob.ILobCoveragesPlugin
uses edge.capabilities.policyjob.binding.dto.PaymentPlanDTO
uses edge.capabilities.quote.mailing.dto.QuoteEmailDTO
uses edge.capabilities.policycommon.validation.IEdgeValidationRulesPlugin
uses edge.capabilities.policycommon.validation.dto.UWIssueDTO
uses edge.capabilities.policycommon.validation.IUWIssuePlugin
uses edge.capabilities.policycommon.validation.dto.JobValidationUnderwritingIssuesDTO

class GatewayQuoteHandler extends QuoteHandler{


  @InjectableNode
  @Param("basePlugin", "Plugin used to manage base quote information")
  @Param("lobPlugin", "Plugin used to manage lob specific information")
  @Param("lobCoveragesUpdatePlugin", "Plugin used to update coverage information")
  @Param("sessionPlugin", "Session management plugin")
  @Param("quotingPlugin", "Quoting process plugin implementation")
  @Param("bindingPlugin", "Binding process plugin")
  @Param("quoteMailingPlugin", "Plugin used to mail the quote")
  @Param("lobMetadataPlugin", "Plugin used to extend metadata generation")
  @Param("authorizer", "Authorizer added to check user can retrieve submission")
  @Param("accContactPlugin", "Plugin used for creating and updating the account holder")
  @Param("validationPlugin", "Plugin used to perform entity validation")
  @Param("aUWIssuePlugin", "Plugin used to perform serialization of UW issues")
  construct(
      basePlugin : IBaseSubmissionPlugin,
      lobPlugin : IQuoteLobDataPlugin<LobDTO>,
      lobCoveragesUpdatePlugin : ILobCoveragesPlugin <LobCoveragesDTO>,
      sessionPlugin : ISessionPlugin,
      quotingPlugin : ISubmissionQuotePlugin,
      bindingPlugin : IBindingPlugin,
      quoteMailingPlugin : IQuoteMailingPlugin,
      lobMetadataPlugin : ILobMetadataPlugin,
      authorizer:IAuthorizerProviderPlugin,
      accContactPlugin: IAccountContactPlugin,
      validationPlugin : IEdgeValidationRulesPlugin,
      aUWIssuePlugin : IUWIssuePlugin) {

    super(
        basePlugin,
        lobPlugin,
        lobCoveragesUpdatePlugin,
        sessionPlugin,
        quotingPlugin,
        bindingPlugin,
        quoteMailingPlugin,
        lobMetadataPlugin,
        authorizer,
        accContactPlugin,
        validationPlugin,
        aUWIssuePlugin
    )
Updated file content:
package edge.capabilities.quote.submission

uses edge.capabilities.policycommon.accountcontact.IAccountContactPlugin
uses edge.capabilities.policycommon.validation.dto.PCValidationResultDTOMapper
uses edge.capabilities.policycommon.validation.dto.PCValidationResultsDTO
uses edge.capabilities.policyjob.quoting.exception.UnderwritingException
uses edge.capabilities.quote.submission.quoting.util.SubmissionUnderwritingIssuesUtil
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser
uses edge.di.annotations.InjectableNode
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.el.Expr
uses edge.aspects.validation.annotations.Context
uses edge.security.authorization.IAuthorizerProviderPlugin
uses edge.capabilities.quote.helper.session.ISessionPlugin
uses edge.security.EffectiveUserProvider
uses edge.capabilities.quote.mailing.IQuoteMailingPlugin
uses edge.capabilities.quote.submission.lob.ILobMetadataPlugin
uses edge.capabilities.quote.submission.dto.QuoteDataDTO
uses edge.capabilities.quote.submission.dto.QuoteRetrievalDTO
uses edge.capabilities.policyjob.binding.IBindingPlugin
uses edge.capabilities.quote.submission.quoting.ISubmissionQuotePlugin
uses edge.capabilities.quote.submission.lob.IQuoteLobDataPlugin
uses edge.capabilities.quote.submission.base.IBaseSubmissionPlugin
uses edge.capabilities.quote.submission.lob.LobDTO
uses edge.capabilities.policyjob.lob.dto.LobCoveragesDTO
uses edge.capabilities.policyjob.lob.ILobCoveragesPlugin
uses edge.capabilities.policyjob.binding.dto.PaymentPlanDTO
uses edge.capabilities.quote.mailing.dto.QuoteEmailDTO
uses edge.capabilities.policycommon.validation.IEdgeValidationRulesPlugin
uses edge.capabilities.policycommon.validation.dto.UWIssueDTO
uses edge.capabilities.policycommon.validation.IUWIssuePlugin
uses edge.capabilities.policycommon.validation.dto.JobValidationUnderwritingIssuesDTO


class GatewayQuoteHandler extends QuoteHandler{


  @InjectableNode
  @Param("basePlugin", "Plugin used to manage base quote information")
  @Param("lobPlugin", "Plugin used to manage lob specific information")
  @Param("lobCoveragesUpdatePlugin", "Plugin used to update coverage information")
  @Param("sessionPlugin", "Session management plugin")
  @Param("quotingPlugin", "Quoting process plugin implementation")
  @Param("bindingPlugin", "Binding process plugin")
  @Param("quoteMailingPlugin", "Plugin used to mail the quote")
  @Param("lobMetadataPlugin", "Plugin used to extend metadata generation")
  @Param("authorizer", "Authorizer added to check user can retrieve submission")
  @Param("accContactPlugin", "Plugin used for creating and updating the account holder")
  @Param("validationPlugin", "Plugin used to perform entity validation")
  @Param("aUWIssuePlugin", "Plugin used to perform serialization of UW issues")
  @Param("aUserProvider", "Provider for the effective user, used to verify account ownership")
  construct(
      basePlugin : IBaseSubmissionPlugin,
      lobPlugin : IQuoteLobDataPlugin<LobDTO>,
      lobCoveragesUpdatePlugin : ILobCoveragesPlugin <LobCoveragesDTO>,
      sessionPlugin : ISessionPlugin,
      quotingPlugin : ISubmissionQuotePlugin,
      bindingPlugin : IBindingPlugin,
      quoteMailingPlugin : IQuoteMailingPlugin,
      lobMetadataPlugin : ILobMetadataPlugin,
      authorizer:IAuthorizerProviderPlugin,
      accContactPlugin: IAccountContactPlugin,
      validationPlugin : IEdgeValidationRulesPlugin,
      aUWIssuePlugin : IUWIssuePlugin,
      aUserProvider: EffectiveUserProvider) {

    super(
        basePlugin,
        lobPlugin,
        lobCoveragesUpdatePlugin,
        sessionPlugin,
        quotingPlugin,
        bindingPlugin,
        quoteMailingPlugin,
        lobMetadataPlugin,
        authorizer,
        accContactPlugin,
        validationPlugin,
        aUWIssuePlugin,
        aUserProvider
    )

DefaultSession plugin changes

The following changes need to be made to /gsrc/edge/capabilities/quote/helper/session/DefaultSessionPlugin.gs‎:

Original file content:
override function getSession(foreignId: String): String {
    final var sess = Bundle.resolveInTransaction(\bundle -> {
      final var mtSession = new PortalSession_MPExt()
      mtSession.foreignId = foreignId
      mtSession.sessionType = DEFAULT_SESSION_TYPE
      mtSession.sessionUUID = UUID.randomUUID().toString()
      mtSession.issueDate = DateUtil.currentDate()
      return mtSession
    })
    return sess.sessionUUID
  }
Updated file content:
override function getSession(foreignId: String): String {
    return getSession(foreignId, null)
  }

  override function getSession(foreignId: String, callerUsername: String): String {
    final var sess = Bundle.resolveInTransaction(\bundle -> {
      final var mtSession = new PortalSession_MPExt()
      mtSession.foreignId = foreignId
      mtSession.sessionType = DEFAULT_SESSION_TYPE
      mtSession.sessionUUID = UUID.randomUUID().toString()
      mtSession.issueDate = DateUtil.currentDate()
      mtSession.username = callerUsername
      return mtSession
    })
    return sess.sessionUUID
  }

ISession plugin changes

The following changes need to be made to /gsrc/edge/capabilities/quote/helper/session/ISessionPlugin.gs‎:

Original file content:
package edge.capabilities.quote.helper.session
uses java.lang.String
uses gw.lang.Returns

/**
 * Service used to work with quote sessions.
 */
interface ISessionPlugin {
  function getSession(foreignId : String):String
  
  /**
   * Validates and refreshes a session.
   */
  @Returns("<code>true</code> iff session was valid (session timeout should be updaded)." +
           "<code>false</code> iff session is not valid")
  function validateAndRefreshSession(sessionUUID : String, foreignId : String)
}
Updated file content:
package edge.capabilities.quote.helper.session
uses java.lang.String
uses gw.lang.Returns

/**
 * Service used to work with quote sessions.
 */
interface ISessionPlugin {
  function getSession(foreignId : String):String

  /**
   * Creates a session bound to a specific caller identity.
   * The callerUsername is stored so it can be verified at retrieval time.
   * Passing null is allowed (anonymous callers).
   */
  function getSession(foreignId : String, callerUsername : String) : String

  /**
   * Validates and refreshes a session.
   */
  @Returns("<code>true</code> iff session was valid (session timeout should be updaded)." +
           "<code>false</code> iff session is not valid")
  function validateAndRefreshSession(sessionUUID : String, foreignId : String)
}

QuoteHandler changes

The following changes need to be made to /gsrc/edge/capabilities/quote/submission/QuoteHandler.gs‎:

Original file content:
package edge.capabilities.quote.submission

uses edge.jsonrpc.annotation.CaptchaCheck
uses edge.capabilities.policycommon.accountcontact.IAccountContactPlugin
uses edge.capabilities.policycommon.availability.ProductCodeUtil
uses edge.capabilities.policycommon.validation.IEdgeValidationRulesPlugin
uses edge.capabilities.policycommon.validation.IUWIssuePlugin
uses edge.capabilities.policycommon.validation.dto.JobValidationUnderwritingIssuesDTO
uses edge.capabilities.policycommon.validation.dto.PCValidationResultDTOMapper
uses edge.capabilities.policycommon.validation.dto.PCValidationResultsDTO
uses edge.capabilities.policycommon.validation.dto.UWIssueDTO
uses edge.capabilities.quote.submission.quoting.util.SubmissionUnderwritingIssuesUtil
uses edge.di.annotations.InjectableNode
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.AbstractRpcHandler
uses edge.PlatformSupport.Bundle
uses edge.PlatformSupport.Logger
uses edge.PlatformSupport.Reflection
uses edge.el.Expr
uses edge.aspects.validation.annotations.Context
uses gw.api.database.Query
uses edge.security.authorization.IAuthorizerProviderPlugin
uses edge.security.authorization.Authorizer
uses edge.exception.EntityNotFoundException
uses edge.jsonrpc.annotation.JsonRpcUnauthenticatedMethod
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.jsonrpc.exception.JsonRpcSecurityException

uses edge.capabilities.quote.helper.session.ISessionPlugin
uses edge.capabilities.quote.helper.questionset.util.QuestionSetUtil
uses edge.capabilities.quote.mailing.IQuoteMailingPlugin
uses edge.capabilities.quote.mailing.dto.QuoteEmailDTO
uses edge.webapimodel.dto.PCWebApiModelDTO
uses edge.capabilities.quote.submission.lob.ILobMetadataPlugin
uses edge.capabilities.quote.submission.dto.QuoteDataDTO
uses edge.capabilities.quote.submission.dto.QuoteRetrievalDTO
uses edge.capabilities.policyjob.binding.IBindingPlugin

uses edge.capabilities.quote.submission.quoting.ISubmissionQuotePlugin
uses edge.capabilities.quote.submission.base.IBaseSubmissionPlugin
uses edge.capabilities.policyjob.quoting.exception.UnderwritingException
uses edge.capabilities.quote.submission.quoting.exception.EntityValidationException
uses edge.capabilities.quote.submission.lob.LobDTO
uses edge.capabilities.quote.submission.quoting.exception.BlockQuoteUnderwritingException
uses edge.capabilities.policyjob.lob.dto.LobCoveragesDTO
uses java.lang.IllegalArgumentException
uses edge.capabilities.policyjob.lob.ILobCoveragesPlugin
uses edge.capabilities.quote.submission.lob.IQuoteLobDataPlugin
uses edge.capabilities.policyjob.quoting.util.QuoteUtil
uses edge.capabilities.policyjob.binding.dto.PaymentPlanDTO
uses edge.capabilities.quote.submission.util.SubmissionUtil
uses gw.util.Pair


class QuoteHandler extends AbstractRpcHandler {

  private static final var LOGGER = new Logger(Reflection.getRelativeName(QuoteHandler))

  /**
   * Validation Rules Plugin
   */
  protected var _validationPlugin : IEdgeValidationRulesPlugin

  /**
   * Used to map underwriting issues and approve/refer issues if permissions allow.
   */
  protected var _uwIssuePlugin: IUWIssuePlugin

  /**
   * A plugin to manage account contacts
   */
  private var _accContactPlugin: IAccountContactPlugin

  /**
   * Base submission plugin used in quote.
   */
  private var _basePlugin: IBaseSubmissionPlugin

  /**
   * Plugin to process LOB specific data.
   */
  protected var _lobPlugin: IQuoteLobDataPlugin<LobDTO>

  /**
   * Plugin to process LOB coverage data.
   */
  private var _lobCoveragesUpdatePlugin: ILobCoveragesPlugin<LobCoveragesDTO>


  /**
   * Session management plugin.
   */
  protected var _sessionPlugin: ISessionPlugin


  /**
   * Quoting plugin.
   */
  protected var _quotingPlugin: ISubmissionQuotePlugin


  /**
   * Submission binding plugin.
   */
  private var _bindingPlugin: IBindingPlugin


  /**
   * Quote mailing plugin.
   */
  private var _quoteMailingPlugin: IQuoteMailingPlugin


  /**
   * Metadata generation extension plugin.
   */
  private var _lobMetadataPlugin: ILobMetadataPlugin

  /**
   * Authorizer
   */
  private var _submissionAuthorizer: Authorizer<Submission>as readonly SubmissionAuthorizer

  construct() { }

  @InjectableNode
  @Param("basePlugin", "Plugin used to manage base quote information")
  @Param("lobPlugin", "Plugin used to manage lob specific information")
  @Param("lobCoveragesUpdatePlugin", "Plugin used to update coverage information")
  @Param("sessionPlugin", "Session management plugin")
  @Param("quotingPlugin", "Quoting process plugin implementation")
  @Param("bindingPlugin", "Binding process plugin")
  @Param("quoteMailingPlugin", "Plugin used to mail the quote")
  @Param("lobMetadataPlugin", "Plugin used to extend metadata generation")
  @Param("authorizer", "Authorizer added to check user can retrieve submission")
  @Param("accContactPlugin", "Plugin used for creating and updating the account holder")
  @Param("validationPlugin", "Plugin used to perform entity validation")
  @Param("aUWIssuePlugin", "Plugin used to perform serialization of UW issues")
  construct(
      basePlugin: IBaseSubmissionPlugin,
      lobPlugin: IQuoteLobDataPlugin<LobDTO>,
      lobCoveragesUpdatePlugin: ILobCoveragesPlugin<LobCoveragesDTO>,
      sessionPlugin: ISessionPlugin,
      quotingPlugin: ISubmissionQuotePlugin,
      bindingPlugin: IBindingPlugin,
      quoteMailingPlugin: IQuoteMailingPlugin,
      lobMetadataPlugin: ILobMetadataPlugin,
      authorizer: IAuthorizerProviderPlugin,
      accContactPlugin: IAccountContactPlugin,
      validationPlugin : IEdgeValidationRulesPlugin,
      aUWIssuePlugin : IUWIssuePlugin
  ) {
    this._basePlugin = basePlugin
    this._lobPlugin = lobPlugin
    this._lobCoveragesUpdatePlugin = lobCoveragesUpdatePlugin
    this._sessionPlugin = sessionPlugin
    this._quotingPlugin = quotingPlugin
    this._bindingPlugin = bindingPlugin
    this._quoteMailingPlugin = quoteMailingPlugin
    this._lobMetadataPlugin = lobMetadataPlugin
    this._submissionAuthorizer = authorizer.authorizerFor(Submission)
    this._accContactPlugin = accContactPlugin
    this._validationPlugin = validationPlugin
    this._uwIssuePlugin = aUWIssuePlugin
  }

  @JsonRpcUnauthenticatedMethod
  @ApidocMethodDescription("Retrieves the meta data required for the quoting handler, including details such as display keys and question sets.")
  @ApidocAvailableSince("5.0")
  function getMetaData(): Object {
    return PCWebApiModelDTO.forTypes(
        {QuoteDataDTO, QuoteEmailDTO, QuoteRetrievalDTO},
        _lobMetadataPlugin.getQuestionSetCodes().map(\qs -> QuestionSetUtil.getQuestionSetByCode(qs))
    )
  }


  /**
   * Creates new submission job and generates a session to be used during the submission process.
   *
   * <dl>
   * <dt>Calls:</dt>
   * <dd><code>IBaseSubmissionPlugin#createSubmission(String,QuoteBaseDataDTO)</code> -
   * to create a new submission passing the base data in <code>qdd.BaseData</code></dd>
   * <dd><code>ILobDataPlugin#updateNewSubmission(PolicyPeriod)</code> - to update the LOB coverables on the base period</dd>
   * <dd><code>ISessionPlugin#getSession(String)</code> - to create the session id which will be added to the
   * returned dto</dd>
   * <dd><code>IBaseSubmissionPlugin#toDTO(Submission)</code> - populates QuoteDataDTO.BaseData, draft data common across LOBs</dd>
   * <dd><code>ILobDataPlugin#toDTO(Submission)</code> - populates QuoteDataDTO.LobDataDTO, draft data specific to LOBs</dd>
   * </dl>
   *
   * @param qdd initial data to create the submission. This implementation only uses the information in <code>qdd.BaseData</code>
   *            and <code>qdd.Lobs</code>
   * @return the DTO for the newly created submission. The contents of the returned value are as follows:
   * <dl>
   * <dt>SessionUUID</dt><dd>the session ID returned by the session plugin</dd>
   * <dt>QuoteID</dt><dd>the JobNumber of the newly created submission</dd>
   * <dt>BaseData</dt><dd>data common across LOBs</dd>
   * <dt>LobDataDTO</dt><dd>LOB coverables and coverages</dd>
   * <dt>QuotingData</dt><dd><code>null</code></dd>
   * <dt>BindingData</dt><dd><code>null</code></dd>
   * <dt>IsSubmitAgent</dt><dd><code>null</code></dd>
   * </dl>
   */
  @JsonRpcUnauthenticatedMethod
  @ApidocMethodDescription("Creates new submission job and generates a session to be used during the submission process.")
  @ApidocAvailableSince("4.0")
  public function create(qdd: QuoteDataDTO): QuoteDataDTO {
    final var draftSubmission = Bundle.resolveInTransaction(\b -> {
      final var versionSpecificProductCode = ProductCodeUtil.getVersionSpecificProductCode(qdd.BaseData.ProductCode)
      var aSubmission = _basePlugin.createSubmission(versionSpecificProductCode, qdd.BaseData)
      _lobPlugin.updateFromDTO(aSubmission.SelectedVersion, qdd.LobData, true)
      return aSubmission
    })
    final var sessId = _sessionPlugin.getSession(draftSubmission.JobNumber)
    return toDTOBaseData(sessId, draftSubmission)
  }

  /**
   * Creates new submission job for an account and generates a session to be used during the submission process. It does not update
   * the account details.
   *
   * <dl>
   * <dt>Calls:</dt>
   * <dd><code>create(qdd : QuoteDataDTO)</code> -
   * to create a new submission passing the data in <code>qdd</code>. </dd>
   * <dd><code>ILobDataPlugin#updateNewSubmission(PolicyPeriod)</code> - to update the LOB coverables on the base period</dd>
   * <dd><code>ISessionPlugin#getSession(String)</code> - to create the session id which will be added to the
   * returned dto</dd>
   * <dd><code>IBaseSubmissionPlugin#toDTO(Submission)</code> - populates QuoteDataDTO.BaseData, draft data common across LOBs</dd>
   * <dd><code>ILobDataPlugin#toDTO(Submission)</code> - populates QuoteDataDTO.LobDataDTO, draft data specific to LOBs</dd>
   * </dl>
   *
   * @param qdd initial data to create the submission. This implementation only uses the information in <code>qdd.BaseData</code>
   *            and <code>qdd.Lobs</code>
   * @return the DTO for the newly created submission. The contents of the returned value are as follows:
   * <dl>
   * <dt>SessionUUID</dt><dd>the session ID returned by the session plugin</dd>
   * <dt>QuoteID</dt><dd>the JobNumber of the newly created submission</dd>
   * <dt>BaseData</dt><dd>draft data common across LOBs</dd>
   * <dt>LobDataDTO</dt><dd>LOB coverables and coverages</dd>
   * <dt>QuotingData</dt><dd><code>null</code></dd>
   * <dt>BindingData</dt><dd><code>null</code></dd>
   * <dt>IsSubmitAgent</dt><dd><code>null</code></dd>
   * </dl>
   */
  @JsonRpcMethod
  @ApidocMethodDescription("Creates new submission job for an account and generates a session to be used during the submission process. It does not update the account detail.")
  @ApidocAvailableSince("4.0")
  public function createForAccount(qdd: QuoteDataDTO): QuoteDataDTO {
    if (qdd.BaseData == null || qdd.BaseData.AccountNumber == null) {
      throw new IllegalArgumentException("Account Number is required.")
    }

    return Bundle.resolveInTransaction(\bundle -> {
      var existingAccount = bundle.add(Account.finder.findAccountByAccountNumber(qdd.BaseData.AccountNumber))

      qdd.BaseData.AccountHolder = _accContactPlugin.toDTO(existingAccount.AccountHolderContact)
      return create(qdd)
    })
  }
/**
   * Retrieves an account submission
   *
   * <dl>
   * <dt>Calls:</dt>
   * <dd><code>retrieve(QuoteRetrievalDTO)</code> - to retrieve the account submission</dd>
   * <dt>Throws:</dt>
   * <dd><code>EntityNotFoundException</code> - if no submission can be found with a matching postal code</dd>
   * </dl>
   *
   * @param qrd data that captures the information needed to retrieve a quote
   * @return the DTO for the updated submission. The contents of the returned value are as follows:
   * <dl>
   * <dt>SessionUUID</dt><dd>the session ID</dd>
   * <dt>QuoteID</dt><dd>the JobNumber of the submission</dd>
   * <dt>BaseData</dt><dd>draft data common across LOBs</dd>
   * <dt>LobDataDTO</dt><dd>LOB coverables and coverages</dd>
   * <dt>QuotingData</dt><dd>the quote data</dd>
   * <dt>BindingData</dt><dd>the bind data</dd>
   * <dt>IsSubmitAgent</dt><dd><code>null</code></dd>
   * </dl>
   */
  @JsonRpcUnauthenticatedMethod
  @ApidocMethodDescription("Retrieves an account submission")
  @ApidocAvailableSince("2021.11.0")
  public function retrieveAccountSubmission(qrd: QuoteRetrievalDTO): QuoteDataDTO {
    final var DEFAULT_EXPIRY_HOURS: int = 1
    final var DEFAULT_SESSION_TYPE: String = "Submission"

    if (!qrd.sessionUUID.HasContent) {
       throw new JsonRpcSecurityException(){:Message = "Invalid session"}
    }

    final var results = Query.make(PortalSession_MPExt).compare("sessionUUID", Equals, qrd.sessionUUID).select()
    if(results.Count != 1) {
        throw new JsonRpcSecurityException(){:Message = "Invalid session"}
    }

    final var session = results.FirstResult

    if (!DEFAULT_SESSION_TYPE.equals(session.sessionType)) {
        throw new JsonRpcSecurityException(){:Message = "Invalid session"}
    }

    if (!gw.api.util.DateUtil.addHours(session.issueDate, DEFAULT_EXPIRY_HOURS).after( Date.CurrentDate)) {
        throw new JsonRpcSecurityException(){:Message = "Invalid session"}
    }

    return this.retrieve(qrd)
  }
Updated file content:
package edge.capabilities.quote.submission

uses edge.jsonrpc.annotation.CaptchaCheck
uses edge.capabilities.policycommon.accountcontact.IAccountContactPlugin
uses edge.capabilities.policycommon.availability.ProductCodeUtil
uses edge.capabilities.policycommon.validation.IEdgeValidationRulesPlugin
uses edge.capabilities.policycommon.validation.IUWIssuePlugin
uses edge.capabilities.policycommon.validation.dto.JobValidationUnderwritingIssuesDTO
uses edge.capabilities.policycommon.validation.dto.PCValidationResultDTOMapper
uses edge.capabilities.policycommon.validation.dto.PCValidationResultsDTO
uses edge.capabilities.policycommon.validation.dto.UWIssueDTO
uses edge.capabilities.quote.submission.quoting.util.SubmissionUnderwritingIssuesUtil
uses edge.di.annotations.InjectableNode
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.AbstractRpcHandler
uses edge.PlatformSupport.Bundle
uses edge.PlatformSupport.Logger
uses edge.PlatformSupport.Reflection
uses edge.el.Expr
uses edge.aspects.validation.annotations.Context
uses gw.api.database.Query
uses edge.security.authorization.IAuthorizerProviderPlugin
uses edge.security.authorization.Authorizer
uses edge.exception.EntityNotFoundException
uses edge.jsonrpc.annotation.JsonRpcUnauthenticatedMethod
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.jsonrpc.exception.JsonRpcSecurityException

uses edge.capabilities.quote.helper.session.ISessionPlugin
uses edge.capabilities.quote.helper.questionset.util.QuestionSetUtil
uses edge.capabilities.quote.mailing.IQuoteMailingPlugin
uses edge.capabilities.quote.mailing.dto.QuoteEmailDTO
uses edge.webapimodel.dto.PCWebApiModelDTO
uses edge.capabilities.quote.submission.lob.ILobMetadataPlugin
uses edge.capabilities.quote.submission.dto.QuoteDataDTO
uses edge.capabilities.quote.submission.dto.QuoteRetrievalDTO
uses edge.capabilities.policyjob.binding.IBindingPlugin

uses edge.capabilities.quote.submission.quoting.ISubmissionQuotePlugin
uses edge.capabilities.quote.submission.base.IBaseSubmissionPlugin
uses edge.capabilities.policyjob.quoting.exception.UnderwritingException
uses edge.capabilities.quote.submission.quoting.exception.EntityValidationException
uses edge.capabilities.quote.submission.lob.LobDTO
uses edge.capabilities.quote.submission.quoting.exception.BlockQuoteUnderwritingException
uses edge.capabilities.policyjob.lob.dto.LobCoveragesDTO
uses java.lang.IllegalArgumentException
uses edge.capabilities.policyjob.lob.ILobCoveragesPlugin
uses edge.capabilities.quote.submission.lob.IQuoteLobDataPlugin
uses edge.capabilities.policyjob.quoting.util.QuoteUtil
uses edge.capabilities.policyjob.binding.dto.PaymentPlanDTO
uses edge.capabilities.quote.submission.util.SubmissionUtil
uses edge.security.EffectiveUserProvider
uses edge.security.authorization.AuthorityType
uses gw.util.Pair

class QuoteHandler extends AbstractRpcHandler {

  private static final var LOGGER = new Logger(Reflection.getRelativeName(QuoteHandler))

  /**
   * Validation Rules Plugin
   */
  protected var _validationPlugin : IEdgeValidationRulesPlugin

  /**
   * Used to map underwriting issues and approve/refer issues if permissions allow.
   */
  protected var _uwIssuePlugin: IUWIssuePlugin

  /**
   * A plugin to manage account contacts
   */
  private var _accContactPlugin: IAccountContactPlugin

  /**
   * Base submission plugin used in quote.
   */
  private var _basePlugin: IBaseSubmissionPlugin

  /**
   * Plugin to process LOB specific data.
   */
  protected var _lobPlugin: IQuoteLobDataPlugin<LobDTO>

  /**
   * Plugin to process LOB coverage data.
   */
  private var _lobCoveragesUpdatePlugin: ILobCoveragesPlugin<LobCoveragesDTO>


  /**
   * Session management plugin.
   */
  protected var _sessionPlugin: ISessionPlugin


  /**
   * Quoting plugin.
   */
  protected var _quotingPlugin: ISubmissionQuotePlugin


  /**
   * Submission binding plugin.
   */
  private var _bindingPlugin: IBindingPlugin


  /**
   * Quote mailing plugin.
   */
  private var _quoteMailingPlugin: IQuoteMailingPlugin


  /**
   * Metadata generation extension plugin.
   */
  private var _lobMetadataPlugin: ILobMetadataPlugin

  /**
   * Authorizer
   */
  private var _submissionAuthorizer: Authorizer<Submission>as readonly SubmissionAuthorizer

  /**
   * Provider for the effective (JWT-authenticated) user, used to verify account ownership.
   */
  protected var _userProvider: EffectiveUserProvider

  construct() { }

  @InjectableNode
  @Param("basePlugin", "Plugin used to manage base quote information")
  @Param("lobPlugin", "Plugin used to manage lob specific information")
  @Param("lobCoveragesUpdatePlugin", "Plugin used to update coverage information")
  @Param("sessionPlugin", "Session management plugin")
  @Param("quotingPlugin", "Quoting process plugin implementation")
  @Param("bindingPlugin", "Binding process plugin")
  @Param("quoteMailingPlugin", "Plugin used to mail the quote")
  @Param("lobMetadataPlugin", "Plugin used to extend metadata generation")
  @Param("authorizer", "Authorizer added to check user can retrieve submission")
  @Param("accContactPlugin", "Plugin used for creating and updating the account holder")
  @Param("validationPlugin", "Plugin used to perform entity validation")
  @Param("aUWIssuePlugin", "Plugin used to perform serialization of UW issues")
  @Param("aUserProvider", "Provider for the effective user, used to verify account ownership")
  construct(
      basePlugin: IBaseSubmissionPlugin,
      lobPlugin: IQuoteLobDataPlugin<LobDTO>,
      lobCoveragesUpdatePlugin: ILobCoveragesPlugin<LobCoveragesDTO>,
      sessionPlugin: ISessionPlugin,
      quotingPlugin: ISubmissionQuotePlugin,
      bindingPlugin: IBindingPlugin,
      quoteMailingPlugin: IQuoteMailingPlugin,
      lobMetadataPlugin: ILobMetadataPlugin,
      authorizer: IAuthorizerProviderPlugin,
      accContactPlugin: IAccountContactPlugin,
      validationPlugin : IEdgeValidationRulesPlugin,
      aUWIssuePlugin : IUWIssuePlugin,
      aUserProvider: EffectiveUserProvider
  ) {
    this._basePlugin = basePlugin
    this._lobPlugin = lobPlugin
    this._lobCoveragesUpdatePlugin = lobCoveragesUpdatePlugin
    this._sessionPlugin = sessionPlugin
    this._quotingPlugin = quotingPlugin
    this._bindingPlugin = bindingPlugin
    this._quoteMailingPlugin = quoteMailingPlugin
    this._lobMetadataPlugin = lobMetadataPlugin
    this._submissionAuthorizer = authorizer.authorizerFor(Submission)
    this._accContactPlugin = accContactPlugin
    this._validationPlugin = validationPlugin
    this._uwIssuePlugin = aUWIssuePlugin
    this._userProvider = aUserProvider
  }

@JsonRpcUnauthenticatedMethod
  @ApidocMethodDescription("Retrieves the meta data required for the quoting handler, including details such as display keys and question sets.")
  @ApidocAvailableSince("5.0")
  function getMetaData(): Object {
    return PCWebApiModelDTO.forTypes(
        {QuoteDataDTO, QuoteEmailDTO, QuoteRetrievalDTO},
        _lobMetadataPlugin.getQuestionSetCodes().map(\qs -> QuestionSetUtil.getQuestionSetByCode(qs))
    )
  }


  /**
   * Creates new submission job and generates a session to be used during the submission process.
   *
   * <dl>
   * <dt>Calls:</dt>
   * <dd><code>IBaseSubmissionPlugin#createSubmission(String,QuoteBaseDataDTO)</code> -
   * to create a new submission passing the base data in <code>qdd.BaseData</code></dd>
   * <dd><code>ILobDataPlugin#updateNewSubmission(PolicyPeriod)</code> - to update the LOB coverables on the base period</dd>
   * <dd><code>ISessionPlugin#getSession(String)</code> - to create the session id which will be added to the
   * returned dto</dd>
   * <dd><code>IBaseSubmissionPlugin#toDTO(Submission)</code> - populates QuoteDataDTO.BaseData, draft data common across LOBs</dd>
   * <dd><code>ILobDataPlugin#toDTO(Submission)</code> - populates QuoteDataDTO.LobDataDTO, draft data specific to LOBs</dd>
   * </dl>
   *
   * @param qdd initial data to create the submission. This implementation only uses the information in <code>qdd.BaseData</code>
   *            and <code>qdd.Lobs</code>
   * @return the DTO for the newly created submission. The contents of the returned value are as follows:
   * <dl>
   * <dt>SessionUUID</dt><dd>the session ID returned by the session plugin</dd>
   * <dt>QuoteID</dt><dd>the JobNumber of the newly created submission</dd>
   * <dt>BaseData</dt><dd>data common across LOBs</dd>
   * <dt>LobDataDTO</dt><dd>LOB coverables and coverages</dd>
   * <dt>QuotingData</dt><dd><code>null</code></dd>
   * <dt>BindingData</dt><dd><code>null</code></dd>
   * <dt>IsSubmitAgent</dt><dd><code>null</code></dd>
   * </dl>
   */
  @JsonRpcUnauthenticatedMethod
  @ApidocMethodDescription("Creates new submission job and generates a session to be used during the submission process.")
  @ApidocAvailableSince("4.0")
  public function create(qdd: QuoteDataDTO): QuoteDataDTO {
    final var draftSubmission = Bundle.resolveInTransaction(\b -> {
      final var versionSpecificProductCode = ProductCodeUtil.getVersionSpecificProductCode(qdd.BaseData.ProductCode)
      var aSubmission = _basePlugin.createSubmission(versionSpecificProductCode, qdd.BaseData)
      _lobPlugin.updateFromDTO(aSubmission.SelectedVersion, qdd.LobData, true)
      return aSubmission
    })
    // Sessions created at quote creation time are not bound to a specific caller because
    // they may be created by an agent on behalf of a customer, or shared via URL.
    // Caller binding is applied later when the customer resumes the quote via retrieve().
    final var sessId = _sessionPlugin.getSession(draftSubmission.JobNumber)
    return toDTOBaseData(sessId, draftSubmission)
  }

  /**
   * Creates new submission job for an account and generates a session to be used during the submission process. It does not update
   * the account details.
   *
   * <dl>
   * <dt>Calls:</dt>
   * <dd><code>create(qdd : QuoteDataDTO)</code> -
   * to create a new submission passing the data in <code>qdd</code>. </dd>
   * <dd><code>ILobDataPlugin#updateNewSubmission(PolicyPeriod)</code> - to update the LOB coverables on the base period</dd>
   * <dd><code>ISessionPlugin#getSession(String)</code> - to create the session id which will be added to the
   * returned dto</dd>
   * <dd><code>IBaseSubmissionPlugin#toDTO(Submission)</code> - populates QuoteDataDTO.BaseData, draft data common across LOBs</dd>
   * <dd><code>ILobDataPlugin#toDTO(Submission)</code> - populates QuoteDataDTO.LobDataDTO, draft data specific to LOBs</dd>
   * </dl>
   *
   * @param qdd initial data to create the submission. This implementation only uses the information in <code>qdd.BaseData</code>
   *            and <code>qdd.Lobs</code>
   * @return the DTO for the newly created submission. The contents of the returned value are as follows:
   * <dl>
   * <dt>SessionUUID</dt><dd>the session ID returned by the session plugin</dd>
   * <dt>QuoteID</dt><dd>the JobNumber of the newly created submission</dd>
   * <dt>BaseData</dt><dd>draft data common across LOBs</dd>
   * <dt>LobDataDTO</dt><dd>LOB coverables and coverages</dd>
   * <dt>QuotingData</dt><dd><code>null</code></dd>
   * <dt>BindingData</dt><dd><code>null</code></dd>
   * <dt>IsSubmitAgent</dt><dd><code>null</code></dd>
   * </dl>
   */
  @JsonRpcMethod
  @ApidocMethodDescription("Creates new submission job for an account and generates a session to be used during the submission process. It does not update the account detail.")
  @ApidocAvailableSince("4.0")
  public function createForAccount(qdd: QuoteDataDTO): QuoteDataDTO {
    if (qdd.BaseData == null || qdd.BaseData.AccountNumber == null) {
      throw new IllegalArgumentException("Account Number is required.")
    }

    final var result = Bundle.resolveInTransaction(\bundle -> {
      var existingAccount = bundle.add(Account.finder.findAccountByAccountNumber(qdd.BaseData.AccountNumber))

      qdd.BaseData.AccountHolder = _accContactPlugin.toDTO(existingAccount.AccountHolderContact)
      return create(qdd)
    })
    // Bind the newly-created session to the account number in the DB so that
    // retrieveAccountSubmission can perform a server-side (non-forgeable) ownership check.
    Bundle.transaction(\bundle -> {
      final var sessResults = Query.make(PortalSession_MPExt).compare("sessionUUID", Equals, result.SessionUUID).select()
      if (sessResults.Count == 1) {
        final var sess = bundle.add(sessResults.FirstResult)
        sess.username = qdd.BaseData.AccountNumber
      }
    })
    return result
  }
/**
   * Retrieves an account submission
   *
   * <dl>
   * <dt>Calls:</dt>
   * <dd><code>retrieve(QuoteRetrievalDTO)</code> - to retrieve the account submission</dd>
   * <dt>Throws:</dt>
   * <dd><code>EntityNotFoundException</code> - if no submission can be found with a matching postal code</dd>
   * </dl>
   *
   * @param qrd data that captures the information needed to retrieve a quote
   * @return the DTO for the updated submission. The contents of the returned value are as follows:
   * <dl>
   * <dt>SessionUUID</dt><dd>the session ID</dd>
   * <dt>QuoteID</dt><dd>the JobNumber of the submission</dd>
   * <dt>BaseData</dt><dd>draft data common across LOBs</dd>
   * <dt>LobDataDTO</dt><dd>LOB coverables and coverages</dd>
   * <dt>QuotingData</dt><dd>the quote data</dd>
   * <dt>BindingData</dt><dd>the bind data</dd>
   * <dt>IsSubmitAgent</dt><dd><code>null</code></dd>
   * </dl>
   */
  @JsonRpcMethod
  @ApidocMethodDescription("Retrieves an account submission")
  @ApidocAvailableSince("2021.11.0")
  public function retrieveAccountSubmission(qrd: QuoteRetrievalDTO): QuoteDataDTO {
    final var effectiveUser = _userProvider.EffectiveUser
    final var DEFAULT_EXPIRY_HOURS: int = 1
    final var DEFAULT_SESSION_TYPE: String = "Submission"

    if (!qrd.sessionUUID.HasContent) {
       throw new JsonRpcSecurityException(){:Message = "Invalid session"}
    }

    final var results = Query.make(PortalSession_MPExt).compare("sessionUUID", Equals, qrd.sessionUUID).select()
    if(results.Count != 1) {
        throw new JsonRpcSecurityException(){:Message = "Invalid session"}
    }

    final var session = results.FirstResult

    // Require a JWT with an ACCOUNT authority that matches this session's account number.
    // The QnB frontend always sends Authorization: Bearer for logged-in users, so
    // effectiveUser carries grantedAuthorities (e.g. "guidewire.edge.account.C000143542.all").
    // Access is denied when:
    //   - the caller is not authenticated (no JWT → no account authorities), or
    //   - the caller's JWT account does not match the session's account (cross-account URL attack).
    if (session.username.HasContent) {
      final var jwtAccountNumbers = effectiveUser?.getTargets(AuthorityType.ACCOUNT)
      if (jwtAccountNumbers == null || jwtAccountNumbers.isEmpty()
          || !jwtAccountNumbers.contains(session.username)) {
        throw new JsonRpcSecurityException(){:Message = "Invalid session"}
      }
    }

   if (!session.foreignId.equals(qrd.QuoteID)) {
      throw new JsonRpcSecurityException(){:Message = "Invalid session"}
    }

    if (!DEFAULT_SESSION_TYPE.equals(session.sessionType)) {
        throw new JsonRpcSecurityException(){:Message = "Invalid session"}
    }

    if (!gw.api.util.DateUtil.addHours(session.issueDate, DEFAULT_EXPIRY_HOURS).after( Date.CurrentDate)) {
        throw new JsonRpcSecurityException(){:Message = "Invalid session"}
    }

    return this.retrieve(qrd)
  }

UnderwritingQuoteRetrievalHandler changes

The following changes need to be made to /gsrc/edge/capabilities/quote/submission/UnderwritingQuoteRetrievalHandler.gs

Original file content:
package edge.capabilities.quote.submission

uses edge.capabilities.gateway.job.submission.ISubmissionRetrievalPlugin
uses edge.capabilities.gateway.policy.IPolicyPeriodRetrievalPlugin
uses edge.capabilities.gateway.policy.IUnderwritingPolicyPeriodPlugin
uses edge.capabilities.industrycode.IIndustryCodePlugin
uses edge.capabilities.policycommon.accountcontact.IAccountContactPlugin
uses edge.capabilities.policycommon.validation.IEdgeValidationRulesPlugin
uses edge.capabilities.policycommon.validation.IUWIssuePlugin
uses edge.capabilities.policyjob.binding.IBindingPlugin
uses edge.capabilities.policyjob.binding.IUnderwritingBindingPlugin
uses edge.capabilities.policyjob.binding.IUnderwritingPaymentPlanPlugin
uses edge.capabilities.policyjob.binding.dto.PaymentPlanDTO
uses edge.capabilities.policyjob.lob.ILobCoveragesPlugin
uses edge.capabilities.policyjob.lob.dto.LobCoveragesDTO
uses edge.capabilities.quote.helper.session.ISessionPlugin
uses edge.capabilities.quote.mailing.IQuoteMailingPlugin
uses edge.capabilities.quote.submission.base.IBaseSubmissionPlugin
uses edge.capabilities.quote.submission.base.IUnderwritingBaseSubmissionPlugin
uses edge.capabilities.quote.submission.dto.QuoteDataDTO
uses edge.capabilities.quote.submission.dto.QuoteRetrievalDTO
uses edge.capabilities.quote.submission.issuing.IIssuePlugin
uses edge.capabilities.quote.submission.lob.ILobMetadataPlugin
uses edge.capabilities.quote.submission.lob.IQuoteLobDataPlugin
uses edge.capabilities.quote.submission.lob.LobDTO
uses edge.capabilities.quote.submission.quotedocument.IQuoteDocumentPlugin
uses edge.capabilities.quote.submission.quoting.ISubmissionQuotePlugin
uses edge.di.annotations.InjectableNode
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.annotation.JsonRpcUnauthenticatedMethod
uses edge.security.authorization.IAuthorizerProviderPlugin

class UnderwritingQuoteRetrievalHandler extends UnderwritingQuoteHandler {

  var _paymentPlanPlugin: IUnderwritingPaymentPlanPlugin

  construct() {
  }

  @InjectableNode
  @Param("basePlugin", "Plugin used to manage base quote information")
  @Param("lobPlugin", "Plugin used to manage lob specific information")
  @Param("lobCoveragesUpdatePlugin", "Plugin used to update coverage information")
  @Param("sessionPlugin", "Session management plugin")
  @Param("quotingPlugin", "Quoting process plugin implementation")
  @Param("bindingPlugin", "Binding process plugin")
  @Param("quoteMailingPlugin", "Plugin used to mail the quote")
  @Param("lobMetadataPlugin", "Plugin used to extend metadata generation")
  @Param("authorizer", "Authorizer added to check user can retrieve submission")
  @Param("accContactPlugin", "Plugin used for creating and updating the account holder")
  @Param("quoteDocumentPlugin", "Plugin used for creating and storing Policy Quote documents")
  @Param("industryCodePlugin", "Plugin used for retrieving industry codes")
  @Param("validationPlugin", "Plugin used to perform entity validation")
  @Param("aUWIssuePlugin", "Plugin used to perform serialization of UW issues")
  construct(
      basePlugin: IBaseSubmissionPlugin,
      underwritingBasePlugin: IUnderwritingBaseSubmissionPlugin,
      lobPlugin: IQuoteLobDataPlugin<LobDTO>,
      lobCoveragesUpdatePlugin: ILobCoveragesPlugin<LobCoveragesDTO>,
      sessionPlugin: ISessionPlugin,
      quotingPlugin: ISubmissionQuotePlugin,
      bindingPlugin: IBindingPlugin,
      quoteMailingPlugin: IQuoteMailingPlugin,
      lobMetadataPlugin: ILobMetadataPlugin,
      authorizer: IAuthorizerProviderPlugin,
      accContactPlugin: IAccountContactPlugin,
      underwritingBindingPlugin: IUnderwritingBindingPlugin,
      issuePlugin: IIssuePlugin,
      policyPeriodRetrievalPlugin: IPolicyPeriodRetrievalPlugin,
      submissionRetrievalPlugin: ISubmissionRetrievalPlugin,
      policyPeriodPlugin: IUnderwritingPolicyPeriodPlugin,
      quoteDocumentPlugin: IQuoteDocumentPlugin,
      industryCodePlugin: IIndustryCodePlugin,
      paymentPlanPlugin: IUnderwritingPaymentPlanPlugin,
      validationPlugin : IEdgeValidationRulesPlugin,
      aUWIssuePlugin : IUWIssuePlugin
  ) {
    super(basePlugin, underwritingBasePlugin, lobPlugin, lobCoveragesUpdatePlugin, sessionPlugin, quotingPlugin, bindingPlugin, quoteMailingPlugin,
        lobMetadataPlugin, authorizer, accContactPlugin, underwritingBindingPlugin, issuePlugin, policyPeriodRetrievalPlugin, submissionRetrievalPlugin,
        policyPeriodPlugin, quoteDocumentPlugin, industryCodePlugin, validationPlugin, aUWIssuePlugin)
    _paymentPlanPlugin = paymentPlanPlugin
  }

  @JsonRpcUnauthenticatedMethod
  @ApidocMethodDescription("Retrieves a submission.")
  @ApidocAvailableSince("10.0 UM-PC Integration")
  public function retrieve(qrd: QuoteRetrievalDTO): QuoteDataDTO {
    var res = super.retrieve(qrd)
    var submission = getSubmissionByJob(res.QuoteID)
    var boundPeriod = submission.ResultingBoundPeriod
    if (boundPeriod != null) {
      _paymentPlanPlugin.updatePaymentPlanWithTaxesAndFees(res.BindData.PaymentPlans.first(), boundPeriod)
    }
    return res
  }

  /**
   * Retrieves the payment plans for the current submission
   *
   * @param quoteID   quoteID of submission that needs to be found
   * @param sessionId current session id
   * @return a list of existing payment plans for the given Submission
   */
  @JsonRpcUnauthenticatedMethod
  @ApidocMethodDescription("Retrieves the payment plans for the given submission.")
  @ApidocAvailableSince("10.0 UM-PC Integration")
  public function retrievePaymentPlans(quoteID: String, sessionId: String): PaymentPlanDTO[] {
    var paymentPlans = super.retrievePaymentPlans(quoteID, sessionId)
    var submission = getSubmissionByJob(quoteID)
    var selectedVersion = submission.SelectedVersion
    if (selectedVersion != null) {
      paymentPlans.each(\plan ->
          _paymentPlanPlugin.updatePaymentPlanWithTaxesAndFees(plan, selectedVersion)
      )
    }
    return paymentPlans
  }

}
package edge.capabilities.quote.submission

uses edge.capabilities.gateway.job.submission.ISubmissionRetrievalPlugin
uses edge.capabilities.gateway.policy.IPolicyPeriodRetrievalPlugin
uses edge.capabilities.gateway.policy.IUnderwritingPolicyPeriodPlugin
uses edge.capabilities.industrycode.IIndustryCodePlugin
uses edge.capabilities.policycommon.accountcontact.IAccountContactPlugin
uses edge.capabilities.policycommon.validation.IEdgeValidationRulesPlugin
uses edge.capabilities.policycommon.validation.IUWIssuePlugin
uses edge.capabilities.policyjob.binding.IBindingPlugin
uses edge.capabilities.policyjob.binding.IUnderwritingBindingPlugin
uses edge.capabilities.policyjob.binding.IUnderwritingPaymentPlanPlugin
uses edge.capabilities.policyjob.binding.dto.PaymentPlanDTO
uses edge.capabilities.policyjob.lob.ILobCoveragesPlugin
uses edge.capabilities.policyjob.lob.dto.LobCoveragesDTO
uses edge.capabilities.quote.helper.session.ISessionPlugin
uses edge.capabilities.quote.mailing.IQuoteMailingPlugin
uses edge.capabilities.quote.submission.base.IBaseSubmissionPlugin
uses edge.capabilities.quote.submission.base.IUnderwritingBaseSubmissionPlugin
uses edge.capabilities.quote.submission.dto.QuoteDataDTO
uses edge.capabilities.quote.submission.dto.QuoteRetrievalDTO
uses edge.capabilities.quote.submission.issuing.IIssuePlugin
uses edge.capabilities.quote.submission.lob.ILobMetadataPlugin
uses edge.capabilities.quote.submission.lob.IQuoteLobDataPlugin
uses edge.capabilities.quote.submission.lob.LobDTO
uses edge.capabilities.quote.submission.quotedocument.IQuoteDocumentPlugin
uses edge.capabilities.quote.submission.quoting.ISubmissionQuotePlugin
uses edge.di.annotations.InjectableNode
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.annotation.JsonRpcUnauthenticatedMethod
uses edge.security.authorization.IAuthorizerProviderPlugin
uses edge.security.EffectiveUserProvider

class UnderwritingQuoteRetrievalHandler extends UnderwritingQuoteHandler {

  var _paymentPlanPlugin: IUnderwritingPaymentPlanPlugin

  construct() {
  }

  @InjectableNode
  @Param("basePlugin", "Plugin used to manage base quote information")
  @Param("lobPlugin", "Plugin used to manage lob specific information")
  @Param("lobCoveragesUpdatePlugin", "Plugin used to update coverage information")
  @Param("sessionPlugin", "Session management plugin")
  @Param("quotingPlugin", "Quoting process plugin implementation")
  @Param("bindingPlugin", "Binding process plugin")
  @Param("quoteMailingPlugin", "Plugin used to mail the quote")
  @Param("lobMetadataPlugin", "Plugin used to extend metadata generation")
  @Param("authorizer", "Authorizer added to check user can retrieve submission")
  @Param("accContactPlugin", "Plugin used for creating and updating the account holder")
  @Param("quoteDocumentPlugin", "Plugin used for creating and storing Policy Quote documents")
  @Param("industryCodePlugin", "Plugin used for retrieving industry codes")
  @Param("validationPlugin", "Plugin used to perform entity validation")
  @Param("aUWIssuePlugin", "Plugin used to perform serialization of UW issues")
  @Param("aUserProvider", "Provider for the effective user, used to verify account ownership")
  construct(
      basePlugin: IBaseSubmissionPlugin,
      underwritingBasePlugin: IUnderwritingBaseSubmissionPlugin,
      lobPlugin: IQuoteLobDataPlugin<LobDTO>,
      lobCoveragesUpdatePlugin: ILobCoveragesPlugin<LobCoveragesDTO>,
      sessionPlugin: ISessionPlugin,
      quotingPlugin: ISubmissionQuotePlugin,
      bindingPlugin: IBindingPlugin,
      quoteMailingPlugin: IQuoteMailingPlugin,
      lobMetadataPlugin: ILobMetadataPlugin,
      authorizer: IAuthorizerProviderPlugin,
      accContactPlugin: IAccountContactPlugin,
      underwritingBindingPlugin: IUnderwritingBindingPlugin,
      issuePlugin: IIssuePlugin,
      policyPeriodRetrievalPlugin: IPolicyPeriodRetrievalPlugin,
      submissionRetrievalPlugin: ISubmissionRetrievalPlugin,
      policyPeriodPlugin: IUnderwritingPolicyPeriodPlugin,
      quoteDocumentPlugin: IQuoteDocumentPlugin,
      industryCodePlugin: IIndustryCodePlugin,
      paymentPlanPlugin: IUnderwritingPaymentPlanPlugin,
      validationPlugin : IEdgeValidationRulesPlugin,
      aUWIssuePlugin : IUWIssuePlugin,
      aUserProvider: EffectiveUserProvider
  ) {
    super(basePlugin, underwritingBasePlugin, lobPlugin, lobCoveragesUpdatePlugin, sessionPlugin, quotingPlugin, bindingPlugin, quoteMailingPlugin,
        lobMetadataPlugin, authorizer, accContactPlugin, underwritingBindingPlugin, issuePlugin, policyPeriodRetrievalPlugin, submissionRetrievalPlugin,
        policyPeriodPlugin, quoteDocumentPlugin, industryCodePlugin, validationPlugin, aUWIssuePlugin, aUserProvider)
    _paymentPlanPlugin = paymentPlanPlugin
  }

  @JsonRpcUnauthenticatedMethod
  @ApidocMethodDescription("Retrieves a submission.")
  @ApidocAvailableSince("10.0 UM-PC Integration")
  public function retrieve(qrd: QuoteRetrievalDTO): QuoteDataDTO {
    var res = super.retrieve(qrd)
    var submission = getSubmissionByJob(res.QuoteID)
    var boundPeriod = submission.ResultingBoundPeriod
    if (boundPeriod != null) {
      _paymentPlanPlugin.updatePaymentPlanWithTaxesAndFees(res.BindData.PaymentPlans.first(), boundPeriod)
    }
    return res
  }

  /**
   * Retrieves the payment plans for the current submission
   *
   * @param quoteID   quoteID of submission that needs to be found
   * @param sessionId current session id
   * @return a list of existing payment plans for the given Submission
   */
  @JsonRpcUnauthenticatedMethod
  @ApidocMethodDescription("Retrieves the payment plans for the given submission.")
  @ApidocAvailableSince("10.0 UM-PC Integration")
  public function retrievePaymentPlans(quoteID: String, sessionId: String): PaymentPlanDTO[] {
    var paymentPlans = super.retrievePaymentPlans(quoteID, sessionId)
    var submission = getSubmissionByJob(quoteID)
    var selectedVersion = submission.SelectedVersion
    if (selectedVersion != null) {
      paymentPlans.each(\plan ->
          _paymentPlanPlugin.updatePaymentPlanWithTaxesAndFees(plan, selectedVersion)
      )
    }
    return paymentPlans
  }

}

UnderwritingQuoteHandler changes

The following changes need to be made to /gsrc/edge/capabilities/quote/submission/UnderwritingQuoteHandler.gs‎:

Original file content:
package edge.capabilities.quote.submission

uses edge.PlatformSupport.Bundle
uses edge.aspects.validation.annotations.Context
uses edge.capabilities.gateway.job.submission.ISubmissionRetrievalPlugin
uses edge.capabilities.gateway.policy.IPolicyPeriodRetrievalPlugin
uses edge.capabilities.gateway.policy.IUnderwritingPolicyPeriodPlugin
uses edge.capabilities.industrycode.IIndustryCodePlugin
uses edge.capabilities.industrycode.dto.IndustryCodeDTO
uses edge.capabilities.industrycode.dto.IndustryCodeSearchCriteriaDTO
uses edge.capabilities.policycommon.accountcontact.IAccountContactPlugin
uses edge.capabilities.policycommon.validation.IEdgeValidationRulesPlugin
uses edge.capabilities.policycommon.validation.IUWIssuePlugin
uses edge.capabilities.policyjob.binding.IBindingPlugin
uses edge.capabilities.policyjob.binding.IUnderwritingBindingPlugin
uses edge.capabilities.policyjob.lob.ILobCoveragesPlugin
uses edge.capabilities.policyjob.lob.dto.LobCoveragesDTO
uses edge.capabilities.policyjob.quoting.exception.UnderwritingException
uses edge.capabilities.policyjob.quoting.util.QuoteUtil
uses edge.capabilities.quote.helper.session.ISessionPlugin
uses edge.capabilities.quote.mailing.IQuoteMailingPlugin
uses edge.capabilities.quote.submission.base.IBaseSubmissionPlugin
uses edge.capabilities.quote.submission.base.IUnderwritingBaseSubmissionPlugin
uses edge.capabilities.quote.submission.dto.IssuanceDTO
uses edge.capabilities.quote.submission.dto.QuoteDataDTO
uses edge.capabilities.quote.submission.dto.QuoteDocumentDTO
uses edge.capabilities.quote.submission.dto.QuoteOnlyRequestDTO
uses edge.capabilities.quote.submission.dto.UnderwritingQuoteDataDTO
uses edge.capabilities.quote.submission.dto.UpdateDraftSubmissionRequestDTO
uses edge.capabilities.quote.submission.dto.UpdateDraftSubmissionResponseDTO
uses edge.capabilities.quote.submission.issuing.IIssuePlugin
uses edge.capabilities.quote.submission.lob.ILobMetadataPlugin
uses edge.capabilities.quote.submission.lob.IQuoteLobDataPlugin
uses edge.capabilities.quote.submission.lob.LobDTO
uses edge.capabilities.quote.submission.quotedocument.IQuoteDocumentPlugin
uses edge.capabilities.quote.submission.quoting.ISubmissionQuotePlugin
uses edge.capabilities.quote.submission.quoting.exception.BlockQuoteUnderwritingException
uses edge.capabilities.quote.submission.quoting.exception.EntityValidationException
uses edge.di.annotations.InjectableNode
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.el.Expr
uses edge.exception.EntityNotFoundException
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser
uses edge.jsonrpc.annotation.JsonRpcUnauthenticatedMethod
uses edge.security.authorization.IAuthorizerProviderPlugin
uses java.lang.IllegalArgumentException
uses java.lang.IllegalStateException
uses gw.api.privacy.EncryptionMaskExpressions

class UnderwritingQuoteHandler extends QuoteHandler {

  var _underwritingBindingPlugin: IUnderwritingBindingPlugin
  var _issuePlugin: IIssuePlugin
  var _policyPeriodRetrievalPlugin: IPolicyPeriodRetrievalPlugin
  var _underwritingBasePlugin: IUnderwritingBaseSubmissionPlugin
  var _submissionRetrievalPlugin: ISubmissionRetrievalPlugin
  var _policyPeriodPlugin: IUnderwritingPolicyPeriodPlugin
  var _quoteDocumentPlugin: IQuoteDocumentPlugin
  var _industryCodePlugin: IIndustryCodePlugin

  construct() {
  }

  @InjectableNode
  @Param("basePlugin", "Plugin used to manage base quote information")
  @Param("lobPlugin", "Plugin used to manage lob specific information")
  @Param("lobCoveragesUpdatePlugin", "Plugin used to update coverage information")
  @Param("sessionPlugin", "Session management plugin")
  @Param("quotingPlugin", "Quoting process plugin implementation")
  @Param("bindingPlugin", "Binding process plugin")
  @Param("quoteMailingPlugin", "Plugin used to mail the quote")
  @Param("lobMetadataPlugin", "Plugin used to extend metadata generation")
  @Param("authorizer", "Authorizer added to check user can retrieve submission")
  @Param("accContactPlugin", "Plugin used for creating and updating the account holder")
  @Param("quoteDocumentPlugin", "Plugin used for creating and storing Policy Quote documents")
  @Param("industryCodePlugin", "Plugin used for retrieving industry codes")
  @Param("validationPlugin", "Plugin used to perform entity validation")
  @Param("aUWIssuePlugin", "Plugin used to perform serialization of UW issues")
  construct(
      basePlugin: IBaseSubmissionPlugin,
      underwritingBasePlugin: IUnderwritingBaseSubmissionPlugin,
      lobPlugin: IQuoteLobDataPlugin<LobDTO>,
      lobCoveragesUpdatePlugin: ILobCoveragesPlugin<LobCoveragesDTO>,
      sessionPlugin: ISessionPlugin,
      quotingPlugin: ISubmissionQuotePlugin,
      bindingPlugin: IBindingPlugin,
      quoteMailingPlugin: IQuoteMailingPlugin,
      lobMetadataPlugin: ILobMetadataPlugin,
      authorizer: IAuthorizerProviderPlugin,
      accContactPlugin: IAccountContactPlugin,
      underwritingBindingPlugin: IUnderwritingBindingPlugin,
      issuePlugin: IIssuePlugin,
      policyPeriodRetrievalPlugin: IPolicyPeriodRetrievalPlugin,
      submissionRetrievalPlugin: ISubmissionRetrievalPlugin,
      policyPeriodPlugin: IUnderwritingPolicyPeriodPlugin,
      quoteDocumentPlugin: IQuoteDocumentPlugin,
      industryCodePlugin: IIndustryCodePlugin,
      validationPlugin : IEdgeValidationRulesPlugin,
      aUWIssuePlugin : IUWIssuePlugin) {
    super(
        basePlugin,
        lobPlugin,
        lobCoveragesUpdatePlugin,
        sessionPlugin,
        quotingPlugin,
        bindingPlugin,
        quoteMailingPlugin,
        lobMetadataPlugin,
        authorizer,
        accContactPlugin,
        validationPlugin,
        aUWIssuePlugin)
    _underwritingBindingPlugin = underwritingBindingPlugin
    _issuePlugin = issuePlugin
    _policyPeriodRetrievalPlugin = policyPeriodRetrievalPlugin
    _underwritingBasePlugin = underwritingBasePlugin
    _submissionRetrievalPlugin = submissionRetrievalPlugin
    _policyPeriodPlugin = policyPeriodPlugin
    _quoteDocumentPlugin = quoteDocumentPlugin
    _industryCodePlugin = industryCodePlugin
  }
Updated file content:
package edge.capabilities.quote.submission

uses edge.PlatformSupport.Bundle
uses edge.aspects.validation.annotations.Context
uses edge.capabilities.gateway.job.submission.ISubmissionRetrievalPlugin
uses edge.capabilities.gateway.policy.IPolicyPeriodRetrievalPlugin
uses edge.capabilities.gateway.policy.IUnderwritingPolicyPeriodPlugin
uses edge.capabilities.industrycode.IIndustryCodePlugin
uses edge.capabilities.industrycode.dto.IndustryCodeDTO
uses edge.capabilities.industrycode.dto.IndustryCodeSearchCriteriaDTO
uses edge.capabilities.policycommon.accountcontact.IAccountContactPlugin
uses edge.capabilities.policycommon.validation.IEdgeValidationRulesPlugin
uses edge.capabilities.policycommon.validation.IUWIssuePlugin
uses edge.capabilities.policyjob.binding.IBindingPlugin
uses edge.capabilities.policyjob.binding.IUnderwritingBindingPlugin
uses edge.capabilities.policyjob.lob.ILobCoveragesPlugin
uses edge.capabilities.policyjob.lob.dto.LobCoveragesDTO
uses edge.capabilities.policyjob.quoting.exception.UnderwritingException
uses edge.capabilities.policyjob.quoting.util.QuoteUtil
uses edge.capabilities.quote.helper.session.ISessionPlugin
uses edge.capabilities.quote.mailing.IQuoteMailingPlugin
uses edge.capabilities.quote.submission.base.IBaseSubmissionPlugin
uses edge.capabilities.quote.submission.base.IUnderwritingBaseSubmissionPlugin
uses edge.capabilities.quote.submission.dto.IssuanceDTO
uses edge.capabilities.quote.submission.dto.QuoteDataDTO
uses edge.capabilities.quote.submission.dto.QuoteDocumentDTO
uses edge.capabilities.quote.submission.dto.QuoteOnlyRequestDTO
uses edge.capabilities.quote.submission.dto.UnderwritingQuoteDataDTO
uses edge.capabilities.quote.submission.dto.UpdateDraftSubmissionRequestDTO
uses edge.capabilities.quote.submission.dto.UpdateDraftSubmissionResponseDTO
uses edge.capabilities.quote.submission.issuing.IIssuePlugin
uses edge.capabilities.quote.submission.lob.ILobMetadataPlugin
uses edge.capabilities.quote.submission.lob.IQuoteLobDataPlugin
uses edge.capabilities.quote.submission.lob.LobDTO
uses edge.capabilities.quote.submission.quotedocument.IQuoteDocumentPlugin
uses edge.capabilities.quote.submission.quoting.ISubmissionQuotePlugin
uses edge.capabilities.quote.submission.quoting.exception.BlockQuoteUnderwritingException
uses edge.capabilities.quote.submission.quoting.exception.EntityValidationException
uses edge.di.annotations.InjectableNode
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.el.Expr
uses edge.exception.EntityNotFoundException
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser
uses edge.jsonrpc.annotation.JsonRpcUnauthenticatedMethod
uses edge.security.authorization.IAuthorizerProviderPlugin
uses edge.security.EffectiveUserProvider
uses java.lang.IllegalArgumentException
uses java.lang.IllegalStateException
uses gw.api.privacy.EncryptionMaskExpressions

class UnderwritingQuoteHandler extends QuoteHandler {

  var _underwritingBindingPlugin: IUnderwritingBindingPlugin
  var _issuePlugin: IIssuePlugin
  var _policyPeriodRetrievalPlugin: IPolicyPeriodRetrievalPlugin
  var _underwritingBasePlugin: IUnderwritingBaseSubmissionPlugin
  var _submissionRetrievalPlugin: ISubmissionRetrievalPlugin
  var _policyPeriodPlugin: IUnderwritingPolicyPeriodPlugin
  var _quoteDocumentPlugin: IQuoteDocumentPlugin
  var _industryCodePlugin: IIndustryCodePlugin

  construct() {
  }

  @InjectableNode
  @Param("basePlugin", "Plugin used to manage base quote information")
  @Param("lobPlugin", "Plugin used to manage lob specific information")
  @Param("lobCoveragesUpdatePlugin", "Plugin used to update coverage information")
  @Param("sessionPlugin", "Session management plugin")
  @Param("quotingPlugin", "Quoting process plugin implementation")
  @Param("bindingPlugin", "Binding process plugin")
  @Param("quoteMailingPlugin", "Plugin used to mail the quote")
  @Param("lobMetadataPlugin", "Plugin used to extend metadata generation")
  @Param("authorizer", "Authorizer added to check user can retrieve submission")
  @Param("accContactPlugin", "Plugin used for creating and updating the account holder")
  @Param("quoteDocumentPlugin", "Plugin used for creating and storing Policy Quote documents")
  @Param("industryCodePlugin", "Plugin used for retrieving industry codes")
  @Param("validationPlugin", "Plugin used to perform entity validation")
  @Param("aUWIssuePlugin", "Plugin used to perform serialization of UW issues")
  @Param("aUserProvider", "Provider for the effective user, used to verify account ownership")
  construct(
      basePlugin: IBaseSubmissionPlugin,
      underwritingBasePlugin: IUnderwritingBaseSubmissionPlugin,
      lobPlugin: IQuoteLobDataPlugin<LobDTO>,
      lobCoveragesUpdatePlugin: ILobCoveragesPlugin<LobCoveragesDTO>,
      sessionPlugin: ISessionPlugin,
      quotingPlugin: ISubmissionQuotePlugin,
      bindingPlugin: IBindingPlugin,
      quoteMailingPlugin: IQuoteMailingPlugin,
      lobMetadataPlugin: ILobMetadataPlugin,
      authorizer: IAuthorizerProviderPlugin,
      accContactPlugin: IAccountContactPlugin,
      underwritingBindingPlugin: IUnderwritingBindingPlugin,
      issuePlugin: IIssuePlugin,
      policyPeriodRetrievalPlugin: IPolicyPeriodRetrievalPlugin,
      submissionRetrievalPlugin: ISubmissionRetrievalPlugin,
      policyPeriodPlugin: IUnderwritingPolicyPeriodPlugin,
      quoteDocumentPlugin: IQuoteDocumentPlugin,
      industryCodePlugin: IIndustryCodePlugin,
      validationPlugin : IEdgeValidationRulesPlugin,
      aUWIssuePlugin : IUWIssuePlugin,
      aUserProvider: EffectiveUserProvider) {
    super(
        basePlugin,
        lobPlugin,
        lobCoveragesUpdatePlugin,
        sessionPlugin,
        quotingPlugin,
        bindingPlugin,
        quoteMailingPlugin,
        lobMetadataPlugin,
        authorizer,
        accContactPlugin,
        validationPlugin,
        aUWIssuePlugin,
        aUserProvider)
    _underwritingBindingPlugin = underwritingBindingPlugin
    _issuePlugin = issuePlugin
    _policyPeriodRetrievalPlugin = policyPeriodRetrievalPlugin
    _underwritingBasePlugin = underwritingBasePlugin
    _submissionRetrievalPlugin = submissionRetrievalPlugin
    _policyPeriodPlugin = policyPeriodPlugin
    _quoteDocumentPlugin = quoteDocumentPlugin
    _industryCodePlugin = industryCodePlugin
  }

EdgeAuthenticationSourceCreator plugin changes

The following changes need to be made to /gsrc/edge/oauth/authplugin/EdgeAuthenticationSourceCreatorPlugin.gs:

Original file content:
package edge.oauth.authplugin

uses gw.auth.gwhub.AuthSourceCreatorPlugin
uses gw.plugin.security.AuthenticationSource
uses jakarta.servlet.http.HttpServletRequest
uses gw.plugin.security.UserNamePasswordAuthenticationSource
uses java.nio.charset.Charset
uses gw.util.Base64Util
uses gw.plugin.security.InvalidAuthenticationSourceData
uses com.nimbusds.jwt.JWTClaimsSet
uses java.lang.Exception
uses edge.servlet.jsonrpc.JsonRpcUnauthenticatedServlet
uses edge.oauth.authplugin.OAuthAuthenticationSource

class EdgeAuthenticationSourceCreatorPlugin extends AuthSourceCreatorPlugin {
  private static final var OAUTH_ATTR_NAME = "OAuthSource"
  private static final var GW_USER_CONTEXT = "GWUserContext"

  override function createSourceFromHTTPRequest(req: HttpServletRequest): AuthenticationSource {
    if (req.getAttribute(JsonRpcUnauthenticatedServlet.UNAUTHENTICATED_USER) != null) {
      return new AnonymousAuthenticationSource()
    }

    var source : AuthenticationSource
    source = getOAuthAuthenticationSource(req)
    if (source == null) {
      source = this.getUserNamePasswordAuthenticationSource(req)
    }
    if (source == null) {
      source = super.createSourceFromHTTPRequest(req)
    }

    return source
  }


  private function getOAuthAuthenticationSource(req: HttpServletRequest): OAuthAuthenticationSource {
    var authHeader = req.getHeader("Authorization")
    var accessToken : String
    if (authHeader != null && authHeader.indexOf("Bearer") != -1) {
      accessToken = authHeader.substring("Bearer".length())?.trim()
    }
    if (req.ParameterNames.toList().contains("access_token")) {
      accessToken = req.getParameter("access_token")
    }
    if(accessToken?.NotBlank) {
      var jwtPlugin = new JwtVerificationPlugin()
      var claimsSet: JWTClaimsSet = null
      try {
        claimsSet = jwtPlugin.verifyToken(accessToken)
      } catch (e: Exception) {
        throw new InvalidAuthenticationSourceData(e)
      }

      var userContextBase64 = req.getHeader(GW_USER_CONTEXT as String)
      var userContext : String
      var oAuthSource : OAuthAuthenticationSource

      if (userContextBase64 != null) {
        userContext = new String(Base64Util.decode(userContextBase64), Charset.forName("UTF-8"))
      }

      oAuthSource = new OAuthAuthenticationSource(claimsSet, userContext)
      req.setAttribute(OAUTH_ATTR_NAME, oAuthSource)
      return oAuthSource
    }
    return null
  }
...
Updated file content:
package edge.oauth.authplugin

uses gw.auth.gwhub.AuthSourceCreatorPlugin
uses gw.plugin.security.AuthenticationSource
uses jakarta.servlet.http.HttpServletRequest
uses gw.plugin.security.UserNamePasswordAuthenticationSource
uses java.nio.charset.Charset
uses gw.util.Base64Util
uses gw.plugin.security.InvalidAuthenticationSourceData
uses com.nimbusds.jwt.JWTClaimsSet
uses java.lang.Exception
uses edge.servlet.jsonrpc.JsonRpcUnauthenticatedServlet
uses edge.oauth.authplugin.OAuthAuthenticationSource

class EdgeAuthenticationSourceCreatorPlugin extends AuthSourceCreatorPlugin {
  private static final var OAUTH_ATTR_NAME = "OAuthSource"
  private static final var GW_USER_CONTEXT = "GWUserContext"

  override function createSourceFromHTTPRequest(req: HttpServletRequest): AuthenticationSource {
    if (req.getAttribute(JsonRpcUnauthenticatedServlet.UNAUTHENTICATED_USER) != null) {
      // Even on the unauthenticated servlet path, attempt to extract and store the JWT
      // as an OAuthAuthenticationSource attribute. This allows the EffectiveUserProvider
      // to be populated with the caller's JWT identity (including ACCOUNT authority claims)
      // for ownership checks, without changing the PC user resolution (still anonymous).
      getOAuthAuthenticationSource(req)
      return new AnonymousAuthenticationSource()
    }

    var source : AuthenticationSource
    source = getOAuthAuthenticationSource(req)
    if (source == null) {
      source = this.getUserNamePasswordAuthenticationSource(req)
    }
    if (source == null) {
      source = super.createSourceFromHTTPRequest(req)
    }

    return source
  }


  private function getOAuthAuthenticationSource(req: HttpServletRequest): OAuthAuthenticationSource {
    var authHeader = req.getHeader("Authorization")
    var accessToken : String
    if (authHeader != null && authHeader.indexOf("Bearer") != -1) {
      accessToken = authHeader.substring("Bearer".length())?.trim()
    }
    if (req.ParameterNames.toList().contains("access_token")) {
      accessToken = req.getParameter("access_token")
    }
    if(accessToken?.NotBlank) {
      var jwtPlugin = new JwtVerificationPlugin()
      var claimsSet: JWTClaimsSet = null
      try {
        claimsSet = jwtPlugin.verifyToken(accessToken)
      } catch (e: Exception) {
        throw new InvalidAuthenticationSourceData(e)
      }

      var userContextBase64 = req.getHeader(GW_USER_CONTEXT as String)
      var userContext : String

      if (userContextBase64 != null) {
        userContext = new String(Base64Util.decode(userContextBase64), Charset.forName("UTF-8"))
      }

      var oAuthSource = new OAuthAuthenticationSource(claimsSet, userContext)
      req.setAttribute(OAUTH_ATTR_NAME, oAuthSource)
      return oAuthSource
    }
    return null
  }
...

DefaultHttpRequestUserIdentity plugin changes

The following changes need to be made to /gsrc/edge/servlet/security/DefaultHttpRequestUserIdentityPlugin.gs:

Original file content:
class DefaultHttpRequestUserIdentityPlugin implements IHttpRequestUserIdentityPlugin {

  private static final var OAUTH_ATTR_NAME = "OAuthSource"
  private static final var GW_USER_CONTEXT = "GWUserContext"


  @ForAllGwNodes
  construct() {
  }

  override function getEffectiveUserFromRequest(req : HttpServletRequest) : EffectiveUser {
    var effectiveUser : EffectiveUser
    var hasOAuthSource = req.getAttribute(OAUTH_ATTR_NAME) != null

    if (hasOAuthSource) {
      return getEffectiveUserFromOAuthSource(req)
    }

    if (req.getAttribute(JsonRpcUnauthenticatedServlet.UNAUTHENTICATED_USER) != null) {
      //anonymous access
      effectiveUser = new EffectiveUser(null, null, null, null)
    } else {
      var currentUser = User.util.CurrentUser
      //Internal user logging in with username and password
      effectiveUser = new EffectiveUser(currentUser.Credential.UserName, null, currentUser, null)
    }
    return effectiveUser
  }

  private function getEffectiveUserFromOAuthSource(req : HttpServletRequest) : EffectiveUser {
    final var currentUser = User.util.CurrentUser
    final var accessToken = getAccessToken(req)
    final var oAuthSource = req.getAttribute(OAUTH_ATTR_NAME) as OAuthAuthenticationSource
    final var userContext = req.getHeader(GW_USER_CONTEXT)

    var userId = getUserId(oAuthSource.Claims)

    if (oAuthSource.IsInternalUser) {
      //Internal user logging in with jwt token issued by xcenter
      return new EffectiveUser(oAuthSource.Username, null, currentUser, userId, accessToken)
    }

    if (oAuthSource.IsServiceUser) {
      var user = oAuthSource.serviceUser
      if (oAuthSource.IsInternalUserForThisApplication) {

        if (user == null) {
          throw new IllegalArgumentException("Access token user identifier not found in request header")
        }
      }
      return new EffectiveUser(user, null, currentUser, userId, accessToken, userContext)
    }

    if (oAuthSource.HasServiceUserAuthorities) {
      if (oAuthSource.serviceUserAuthorities == null) {
        throw new IllegalArgumentException("Access token user authorities not found in request header")
      }

      var grantedAuthorities = getGrantedAuthorities(oAuthSource.Claims, oAuthSource.serviceUserAuthorities)
      return new EffectiveUser(oAuthSource.Username, grantedAuthorities, null, userId, accessToken, userContext)
    }
Updated file content:
class DefaultHttpRequestUserIdentityPlugin implements IHttpRequestUserIdentityPlugin {

  private static final var OAUTH_ATTR_NAME = "OAuthSource"
  private static final var GW_USER_CONTEXT = "GWUserContext"


  @ForAllGwNodes
  construct() {
  }

  override function getEffectiveUserFromRequest(req : HttpServletRequest) : EffectiveUser {
    var effectiveUser : EffectiveUser
    var hasOAuthSource = req.getAttribute(OAUTH_ATTR_NAME) != null

    if (hasOAuthSource) {
      return getEffectiveUserFromOAuthSource(req)
    }

    if (req.getAttribute(JsonRpcUnauthenticatedServlet.UNAUTHENTICATED_USER) != null) {
      //anonymous access
      effectiveUser = new EffectiveUser(null, null, null, null)
    } else {
      var currentUser = User.util.CurrentUser
      //Internal user logging in with username and password (no prior JWT on this session)
      effectiveUser = new EffectiveUser(currentUser.Credential.UserName, null, currentUser, null)
    }
    return effectiveUser
  }

  private function getEffectiveUserFromOAuthSource(req : HttpServletRequest) : EffectiveUser {
    final var currentUser = User.util.CurrentUser
    final var accessToken = getAccessToken(req)
    final var oAuthSource = req.getAttribute(OAUTH_ATTR_NAME) as OAuthAuthenticationSource
    final var userContext = req.getHeader(GW_USER_CONTEXT)

    var userId = getUserId(oAuthSource.Claims)

    if (oAuthSource.IsInternalUser) {
      //Internal user logging in with jwt token issued by xcenter.
      // Also parse grantedAuthorities from the JWT when present (e.g. portal users
      // who have a pcUserName claim AND account-scoped authorities in the same token).
      var internalGrantedAuthorities = oAuthSource.Claims.Claims.get("grantedAuthorities") != null
          ? getGrantedAuthorities(oAuthSource.Claims, null)
          : null
      return new EffectiveUser(oAuthSource.Username, internalGrantedAuthorities, currentUser, userId, accessToken)
    }

    if (oAuthSource.IsServiceUser) {
      var user = oAuthSource.serviceUser
      if (oAuthSource.IsInternalUserForThisApplication) {

        if (user == null) {
          throw new IllegalArgumentException("Access token user identifier not found in request header")
        }
      }
      // Parse grantedAuthorities from the JWT when present — service/portal users
      // may have account-scoped authorities alongside the service-user scope.
      var serviceGrantedAuthorities = oAuthSource.Claims.Claims.get("grantedAuthorities") != null
          ? getGrantedAuthorities(oAuthSource.Claims, null)
          : null
      return new EffectiveUser(user, serviceGrantedAuthorities, currentUser, userId, accessToken, userContext)
    }

    if (oAuthSource.HasServiceUserAuthorities) {
      if (oAuthSource.serviceUserAuthorities == null) {
        throw new IllegalArgumentException("Access token user authorities not found in request header")
      }

      var grantedAuthorities = getGrantedAuthorities(oAuthSource.Claims, oAuthSource.serviceUserAuthorities)
      return new EffectiveUser(oAuthSource.Username, grantedAuthorities, null, userId, accessToken, userContext)
    }

HOWizard changes

The following changes need to be made to applications/common/capabilities-react/gw-capability-quoteandbind-ho-react/HOWizard.jsx:

Original file content:
function HOWizard(props) {
    const {
        showConfirm,
        showAlert
    } = useModal();

    const { steps, title } = wizardConfig;
    const [initialSubmission, setInitialSubmission] = useState(null);
    const [isLoading, setIsLoading] = useState(true);
    const [shouldSkipValidSteps, setShouldSkipValidSteps] = useState(false);
    const viewModelService = useContext(ViewModelServiceContext);
    const { location, history } = props;
    useEffect(() => {
        const viewModelContext = {
            AccountEmailRequired: true,
            AccountDOBRequired: true
        };

        let submissionPromise;

        if (!viewModelService) {
            if (_.isEmpty(location.search)) {
                history.push('/');
            }

            return;
        }

        if (_.has(location, 'state.address')) {
            const { address } = location.state;
            submissionPromise = createSubmissionOnPartialAddress(address);
        } else if (_.has(location, 'state.submission')) {
            const { submission } = location.state;
            setShouldSkipValidSteps(true);
            submissionPromise = Promise.resolve(submission);
        } else if (_.has(location, 'search')) {
            const parsedParms = queryString.parse(location.search);
            submissionPromise = LoadSaveService.retrieveAccountSubmission(parsedParms);
        } else {
            return;
        }

        submissionPromise.then((response) => {
            const submissionVM = viewModelService.create(
                removeMockData(response),
                'pc',
                'edge.capabilities.quote.submission.dto.QuoteDataDTO',
                viewModelContext
            );

            setInitialSubmission(submissionVM);
            setIsLoading(false);
        });
        // only execute this once per component lifecycle
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [viewModelService]);
...
Updated file content:
function HOWizard(props) {
    const {
        showConfirm,
        showAlert
    } = useModal();

    const { steps, title } = wizardConfig;
    const [initialSubmission, setInitialSubmission] = useState(null);
    const [isLoading, setIsLoading] = useState(true);
    const [shouldSkipValidSteps, setShouldSkipValidSteps] = useState(false);
    const viewModelService = useContext(ViewModelServiceContext);
    const { location, history } = props;

    const [qnbAuthHeader] = useState(() => {
        // sessionStorage survives StrictMode's double-invocation of state initializers.
        // First invocation: reads hash, stores token, clears hash (result discarded by StrictMode).
        // Second invocation: reads from sessionStorage, removes it, returns the token.
        const stored = sessionStorage.getItem('qnbToken');
        if (stored) {
            sessionStorage.removeItem('qnbToken');
            return { Authorization: `Bearer ${stored}` };
        }
        const fragment = window.location.hash;
        if (fragment) {
            const hashParams = new URLSearchParams(fragment.slice(1));
            const token = hashParams.get('t');
            if (token) {
                const decoded = decodeURIComponent(token);
                window.history.replaceState({}, '', window.location.pathname + window.location.search);
                sessionStorage.setItem('qnbToken', decoded);
                return { Authorization: `Bearer ${decoded}` };
            }
        }
        return {};
    });

    useEffect(() => {
        const viewModelContext = {
            AccountEmailRequired: true,
            AccountDOBRequired: true
        };

        let submissionPromise;

        if (!viewModelService) {
            if (_.isEmpty(location.search)) {
                history.push('/');
            }

            return;
        }

        if (_.has(location, 'state.address')) {
            const { address } = location.state;
            submissionPromise = createSubmissionOnPartialAddress(address);
        } else if (_.has(location, 'state.submission')) {
            const { submission } = location.state;
            setShouldSkipValidSteps(true);
            submissionPromise = Promise.resolve(submission);
        } else if (_.has(location, 'search')) {
            const parsedParms = queryString.parse(location.search);
            submissionPromise = LoadSaveService.retrieveAccountSubmission(parsedParms, qnbAuthHeader);
        } else {
            return;
        }
        submissionPromise
            .then((response) => {
                const submissionVM = viewModelService.create(
                    removeMockData(response),
                    'pc',
                    'edge.capabilities.quote.submission.dto.QuoteDataDTO',
                    viewModelContext
                );

                setInitialSubmission(submissionVM);
                setIsLoading(false);
            })
            .catch(() => {
                setIsLoading(false);
                history.push('/contact-us');
            });
        // only execute this once per component lifecycle
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [viewModelService]);
...

PAWizard changes

The following changes need to be made to applications/common/capabilities-react/gw-capability-quoteandbind-pa-react/PAWizard.jsx:

Original file content:
function createSubmissionOnPartialAddress(address) {
    const data = setPrepData(address);
    return LoadSaveService.createSubmission(data);
}

function PAWizard(props) {
    const {
        showConfirm,
        showAlert
    } = useModal();

    const { steps, title } = wizardConfig;
    const [initialSubmission, setInitialSubmission] = useState(null);
    const [isLoading, setIsLoading] = useState(true);
    const [shouldSkipValidSteps, setShouldSkipValidSteps] = useState(false);
    const viewModelService = useContext(ViewModelServiceContext);
    const { location, history } = props;
    useEffect(() => {
        const viewModelContext = {
            AccountEmailRequired: true,
            DriverEmailRequired: true,
            AccountDOBRequired: true
        };

        if (!viewModelService) {
            if (_.isEmpty(location.search)) {
                history.push('/');
            }

            return;
        }

        let submissionPromise;
        if (_.has(location, 'state.address')) {
            const { address } = location.state;
            submissionPromise = createSubmissionOnPartialAddress(address);
        } else if (_.has(location, 'state.submission')) {
            const { submission } = location.state;
            setShouldSkipValidSteps(true);
            submissionPromise = Promise.resolve(submission);
        } else if (_.has(location, 'search')) {
            const parsedParms = queryString.parse(location.search);
            submissionPromise = LoadSaveService.retrieveAccountSubmission(parsedParms);
        } else {
            return;
        }

        submissionPromise.then((response) => {
            const submissionVM = viewModelService.create(
                removeMockData(response),
                'pc',
                'edge.capabilities.quote.submission.dto.QuoteDataDTO',
                viewModelContext
            );

            setInitialSubmission(submissionVM);
            setIsLoading(false);
        });
        // only execute this once per component lifecycle
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [viewModelService]);
...
Updated file content:
function createSubmissionOnPartialAddress(address) {
    const data = setPrepData(address);
    return LoadSaveService.createSubmission(data);
}

function PAWizard(props) {
    const {
        showConfirm,
        showAlert
    } = useModal();

    const { steps, title } = wizardConfig;
    const [initialSubmission, setInitialSubmission] = useState(null);
    const [isLoading, setIsLoading] = useState(true);
    const [shouldSkipValidSteps, setShouldSkipValidSteps] = useState(false);
    const viewModelService = useContext(ViewModelServiceContext);
    const { location, history } = props;

    const [qnbAuthHeader] = useState(() => {
      // sessionStorage survives StrictMode's double-invocation of state initializers.
      // First invocation: reads hash, stores token, clears hash (result discarded by StrictMode).
      // Second invocation: reads from sessionStorage, removes it, returns the token.
      const stored = sessionStorage.getItem('qnbToken');
      if (stored) {
          sessionStorage.removeItem('qnbToken');
          return { Authorization: `Bearer ${stored}` };
      }
      const fragment = window.location.hash;
      if (fragment) {
          const hashParams = new URLSearchParams(fragment.slice(1));
          const token = hashParams.get('t');
          if (token) {
              const decoded = decodeURIComponent(token);
              window.history.replaceState({}, '', window.location.pathname + window.location.search);
              sessionStorage.setItem('qnbToken', decoded);
              return { Authorization: `Bearer ${decoded}` };
          }
      }
      return {};
  });

    useEffect(() => {
        const viewModelContext = {
            AccountEmailRequired: true,
            DriverEmailRequired: true,
            AccountDOBRequired: true
        };

        if (!viewModelService) {
            if (_.isEmpty(location.search)) {
                history.push('/');
            }

            return;
        }

        let submissionPromise;
        if (_.has(location, 'state.address')) {
            const { address } = location.state;
            submissionPromise = createSubmissionOnPartialAddress(address);
        } else if (_.has(location, 'state.submission')) {
            const { submission } = location.state;
            setShouldSkipValidSteps(true);
            submissionPromise = Promise.resolve(submission);
        } else if (_.has(location, 'search')) {
              const parsedParms = queryString.parse(location.search);
              submissionPromise = LoadSaveService.retrieveAccountSubmission(parsedParms, qnbAuthHeader);
          } 
          submissionPromise
              .then((response) => {
                  const submissionVM = viewModelService.create(
                      removeMockData(response),
                      'pc',
                      'edge.capabilities.quote.submission.dto.QuoteDataDTO',
                      viewModelContext
                  );
                  setInitialSubmission(submissionVM);
                  setIsLoading(false);
              })
              .catch(() => {
                  setIsLoading(false);
                  history.push('/contact-us');
              });
        // only execute this once per component lifecycle
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [viewModelService]);
...

Updated package versions

Guidewire has modified the configuration of certain package versions for improved product security. Customers who maintain customized back-end deployments must apply these changes manually.

JDK18on package update

  • Package: org.bouncycastle:bcprov-jdk18on
  • Updated version: 1.84
  • File changed: Capabilities/gpa-policy/pom.xml
  • Change made:
    <dependency>
       <groupId>org.bouncycastle</groupId>
       <artifactId>bcprov-jdk18on</artifactId>
    -            <version>1.78</version>
    +            <version>1.84</version>
     </dependency>

Nimbus JWT package update

  • Package: com.nimbusds:nimbus-jose-jwt
  • Updated version: 9.37.4
  • File changed: Platform/pom.xml
  • Change made:
    <dependency>
       <groupId>com.nimbusds</groupId>
       <artifactId>nimbus-jose-jwt</artifactId>
    -            <version>9.37.2</version>
    +            <version>9.37.4</version>
     </dependency>

2025.07.4 release

This section lists all of the changes added in patch 4 of the 2025.07 release.

New Jutro version

Digital reference applications now use Jutro patch version 10.11.3-next-20260305184711. This brings enhanced security to the Jutro Design System.

Response header configuration changes

Guidewire has modified the configuration of certain response headers so that file uploads are better sanitized. There are steps that you can take to have better sanitized file uploads and need to manually add the changes to your own files. The changes need to be added to the following files:
  • gsrc/edge/capabilities/gateway/document/DocumentUploadHandler.gs
  • gsrc/edge/capabilities/policy/document/PolicyDocumentUploadHandler.gs
  • gsrc/edge/capabilities/granite_on_cloud-servicerequest/document/ServiceRequestFileUploadHandler.gs
  • gsrc/edge/security/fileupload/FileUploadUtil.gs
  • gsrc/edge/capabilities/claim/document/ClaimDocumentUploadHandler.gs‎

Changes to the document upload handler

The following sections show original code and updated code. Each bold line indicates a change that needs to be made to addresses this fix. Make these changes in DocumentUploadHandler.gs:

The following codeblock contains the original file content. Highlighted text indicates content removed or changed in the updated file:
package edge.capabilities.gateway.document

uses edge.jsonrpc.AbstractRpcHandler
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.capabilities.document.dto.DocumentBaseDTO
uses edge.jsonrpc.exception.JsonRpcSecurityException
uses org.apache.commons.fileupload2.core.FileItem
uses gw.plugin.document.IDocumentContentSource
uses gw.plugin.Plugins
uses edge.capabilities.gateway.document.dto.DocumentDTO
uses edge.di.annotations.InjectableNode
uses edge.PlatformSupport.Bundle
uses edge.security.fileupload.IFileUploadPlugin
uses edge.capabilities.document.IDocumentSessionPlugin
uses edge.security.authorization.exception.AuthorizationException
uses edge.security.fileupload.exception.IllegalContentTypeException
uses edge.security.fileupload.exception.FileNameMismatchException
uses gw.api.locale.DisplayKey

class DocumentUploadHandler extends AbstractRpcHandler {

  var _documentPlugin : IDocumentPlugin
  private var _documentSessionPlugin : IDocumentSessionPlugin
  private var _fileUploadPlugin : IFileUploadPlugin

  @InjectableNode
  @Param("documentSessionPlugin", "Plugin used to deal with document sessions")
  @Param("fileUploadPlugin", "Plugin used to verify file upload validity")
  construct(aDocumentPlugin : IDocumentPlugin,
            documentSessionPlugin : IDocumentSessionPlugin,
            fileUploadPlugin : IFileUploadPlugin){
    this._documentPlugin = aDocumentPlugin
    this._documentSessionPlugin = documentSessionPlugin
    this._fileUploadPlugin = fileUploadPlugin
  }

  /**
   * Uploads a document
   *
   * <dl>
   *   <dt>Calls:</dt>
   * <dd> <code>IDocumentPlugin#createDocument(DocumentDTO)</code> - To create a document</dd>
   * <dd> <code>IDocumentPlugin#toDTO(Document)</code> - To return DTO with document </dd>
   * <dd> <code>IDocumentContentSource#addDocument(InputStream, Document)</code> - To add Document</dd>
   * <dt>Throws:</dt>
   *  <dd><code>IllegalArgumentException</code> - If claim number associated with the document is null or empty</dd>
   *  <dd><code>EntityNotFoundException</code> - If no claim is found</dd>
   *  <dd><code>AuthorizationException</code> - If the portal user has no access to the claim</dd>
   * </dl>
   * @param documentDTO
   * @param fileItem
   * @return document metadata response
   */
  @JsonRpcRunAsInternalGWUser
  @JsonRpcMethod
  @ApidocMethodDescription("Uploads a document. NOTE: Given a list of file items, it expects that the element " +
      "at [0] is the document metadata; at [1] is the item to be uploaded")
  @ApidocAvailableSince("5.0")
  function upload(documentDto: DocumentDTO, documentFile: FileItem): DocumentBaseDTO {

    try {
      // validate upload token is still valid
      _documentSessionPlugin.getSessionDocumentId(documentDto.SessionID)
    } catch (ex : JsonRpcSecurityException){
      throw new AuthorizationException(){:Message = "Unauthorized portal access"}
    }

    if(documentDto.Name!=documentFile.Name){
      throw new FileNameMismatchException(DisplayKey.get("Document.upload.fileName.error", documentFile.Name))
    }

    if(documentDto.MimeType!=documentFile.ContentType) {
      throw new IllegalContentTypeException(DisplayKey.get("Document.upload.contentType.error", documentFile.ContentType))
    }

    if (!_fileUploadPlugin.canUploadContentType(documentFile)) {
      throw new IllegalContentTypeException("Cannot upload files of content type: " + documentFile.ContentType)
    }

    /** Some browsers (or fileupload library on some browsers) do not populate this field). */
    if (documentDto.MimeType == null) {
      documentDto.MimeType = documentFile.ContentType
    }

    final var doc = Bundle.resolveInTransaction( \ bundle -> {
      try {
        final var _doc = _documentPlugin.createDocument(documentDto)
        // add the document to the dms
        Plugins.get(IDocumentContentSource).addDocument(documentFile.InputStream, _doc)
        return _doc
      } finally {
        documentFile.InputStream.close()
      }
    })
    return _documentPlugin.toDTO(doc)
  }
}
The following codeblock contains the updated file content. Highlighted text indicates updates:
package edge.capabilities.gateway.document

uses edge.jsonrpc.AbstractRpcHandler
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.capabilities.document.dto.DocumentBaseDTO
uses edge.jsonrpc.exception.JsonRpcSecurityException
uses org.apache.commons.fileupload2.core.FileItem
uses gw.plugin.document.IDocumentContentSource
uses gw.plugin.Plugins
uses edge.capabilities.gateway.document.dto.DocumentDTO
uses edge.di.annotations.InjectableNode
uses edge.PlatformSupport.Bundle
uses edge.capabilities.document.IDocumentSessionPlugin
uses edge.security.authorization.exception.AuthorizationException
uses edge.security.fileupload.exception.IllegalContentTypeException
uses edge.security.fileupload.exception.FileNameMismatchException
uses gw.api.locale.DisplayKey
uses org.apache.tika.Tika
uses gw.api.util.Logger
uses edge.security.fileupload.DefaultFileUploadPlugin
uses edge.security.fileupload.FileUploadUtil

class DocumentUploadHandler extends AbstractRpcHandler {

  private static final var LOG = Logger.forCategory(DocumentUploadHandler.Type.QName)
  private static final var TIKA = new Tika()
    var _documentPlugin : IDocumentPlugin
    private var _documentSessionPlugin : IDocumentSessionPlugin
    static final var MIME_PNG = "image/png"
    static final var MIME_JPEG = "image/jpeg"
    static final var MIME_PDF = "application/pdf"
  
    @InjectableNode
    @Param("documentSessionPlugin", "Plugin used to deal with document sessions")
    construct(aDocumentPlugin : IDocumentPlugin,
              documentSessionPlugin : IDocumentSessionPlugin){
      this._documentPlugin = aDocumentPlugin
      this._documentSessionPlugin = documentSessionPlugin
    }
  
    /**
     * Uploads a document
     *
     * <dl>
     *   <dt>Calls:</dt>
     * <dd> <code>IDocumentPlugin#createDocument(DocumentDTO)</code> - To create a document</dd>
     * <dd> <code>IDocumentPlugin#toDTO(Document)</code> - To return DTO with document </dd>
     * <dd> <code>IDocumentContentSource#addDocument(InputStream, Document)</code> - To add Document</dd>
     * <dt>Throws:</dt>
     *  <dd><code>IllegalArgumentException</code> - If claim number associated with the document is null or empty</dd>
     *  <dd><code>EntityNotFoundException</code> - If no claim is found</dd>
     *  <dd><code>AuthorizationException</code> - If the portal user has no access to the claim</dd>
     * </dl>
     * @param documentDTO
     * @param fileItem
     * @return document metadata response
     */
    @JsonRpcRunAsInternalGWUser
    @JsonRpcMethod
    @ApidocMethodDescription("Uploads a document. NOTE: Given a list of file items, it expects that the element " +
        "at [0] is the document metadata; at [1] is the item to be uploaded")
    @ApidocAvailableSince("5.0")
    function upload(documentDto: DocumentDTO, documentFile: FileItem): DocumentBaseDTO {
  
      try {
        // validate upload token is still valid
        _documentSessionPlugin.getSessionDocumentId(documentDto.SessionID)
      } catch (ex : JsonRpcSecurityException){
        throw new AuthorizationException(){:Message = "Unauthorized portal access"}
      }
  
      if (documentDto.Name != documentFile.Name) {
        throw new FileNameMismatchException(DisplayKey.get("Document.upload.fileName.error", documentFile.Name))
      }

      FileUploadUtil.assertSafeFileName(documentDto.Name)
      FileUploadUtil.assertSafeFileName(documentFile.Name)
  
      if (documentDto.MimeType == null) {
        documentDto.MimeType = documentFile.ContentType
      }
  
      FileUploadUtil.enforceMaxUploadSize(documentFile)
  
      var bytes = FileUploadUtil.readAllBytesAndCloseWithLimit(documentFile, FileUploadUtil.getMaxUploadBytes())
  
      // Pass filename so we can disambiguate OOXML when Tika reports application/zip.
      var detectedType = FileUploadUtil.normalizeMimeType(TIKA.detect(bytes), documentFile.Name)
  
      FileUploadUtil.enforcePdfNameConsistency(documentFile.Name, detectedType, MIME_PDF)
      FileUploadUtil.assertExtensionMatchesType(documentFile.Name, detectedType)

      if (!isAllowedDetectedType(detectedType)) {
        throw new IllegalContentTypeException("Cannot upload files of detected content type: " + detectedType)
      }
  
      FileUploadUtil.validateContent(detectedType, bytes, MIME_PDF, MIME_PNG, MIME_JPEG)
  
      documentDto.MimeType = detectedType
  
      final var doc = Bundle.resolveInTransaction(\ bundle -> {
        final var _doc = _documentPlugin.createDocument(documentDto)
        var uploadInputStream = new java.io.ByteArrayInputStream(bytes)
        try {
          Plugins.get(IDocumentContentSource).addDocument(uploadInputStream, _doc)
        } finally {
          uploadInputStream.close()
        }
        return _doc
      })
  
      return _documentPlugin.toDTO(doc)
    }
  
    private static function isAllowedDetectedType(detectedType : String) : boolean {
      return new DefaultFileUploadPlugin().canUploadContentType(detectedType)
  }
}

Changes to the policy document upload handler

The following sections show both the original code as well as the updated code. Each bold line indicates a change required to address this issue. Make these changes in PolicyDocumentUploadHandler.gs:

The following codeblock contains the original file content. Highlighted text indicates content removed or changed in the updated file:
package edge.capabilities.policy.document

uses edge.jsonrpc.AbstractRpcHandler
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.exception.JsonRpcSecurityException
uses org.apache.commons.fileupload2.core.FileItem
uses edge.di.annotations.InjectableNode
uses gw.api.webservice.exception.BadIdentifierException
uses edge.capabilities.policy.auth.IPolicyAccessPlugin
uses edge.security.authorization.exception.NoAuthorityException
uses edge.capabilities.policy.document.dto.PolicyDocumentDTO
uses gw.plugin.Plugins
uses gw.plugin.document.IDocumentContentSource
uses edge.capabilities.policy.document.dto.PolicyDocumentUploadDTO
uses edge.PlatformSupport.Bundle
uses edge.capabilities.document.IDocumentSessionPlugin
uses edge.security.authorization.exception.AuthorizationException
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.capabilities.policy.util.PolicyUtil
uses edge.security.fileupload.exception.IllegalContentTypeException
uses edge.security.fileupload.exception.FileNameMismatchException
uses gw.api.locale.DisplayKey
uses edge.security.fileupload.IFileUploadPlugin

/**
 * Handler of document uploads.
 * It is needed to work around "security through obscurity" in the authz service and its inability
 * to cope with many different access modes for the same URL. It is also a workaround for a third-party component
 * which is also inflexible and could not accommodate different transports.
 */
class PolicyDocumentUploadHandler extends AbstractRpcHandler {
  private var _policyAuthCheck : IPolicyAccessPlugin
  private var _documentPlugin : IPolicyDocumentsPlugin
  private var _documentSessionPlugin : IDocumentSessionPlugin
  private var _fileUploadPlugin : IFileUploadPlugin

  @InjectableNode
  @Param("policyAuthCheck", "Policy access policy")
  @Param("documentPlugin", "Plugin used to access documents associated with the policy")
  @Param("documentSessionPlugin", "Document session management plugin")
  construct(policyAuthCheck : IPolicyAccessPlugin, documentPlugin : IPolicyDocumentsPlugin,
            documentSessionPlugin : IDocumentSessionPlugin, fileUploadPlugin : IFileUploadPlugin) {
    this._policyAuthCheck = policyAuthCheck
    this._documentPlugin = documentPlugin
    this._documentSessionPlugin = documentSessionPlugin
    this._fileUploadPlugin = fileUploadPlugin
  }


  @JsonRpcMethod
  @ApidocMethodDescription("Uploads a document. " +
      "NOTE: Given a list of file items, it expects that the element at [0] is the document metadata and at [1] is the item to be uploaded.")
  @ApidocAvailableSince("5.0")
  function upload(documentDto:PolicyDocumentUploadDTO, documentFile: FileItem) : PolicyDocumentDTO {
    try {
      try {
        _documentSessionPlugin.getSessionDocumentId(documentDto.SessionID)
      } catch (ex : JsonRpcSecurityException) {
        throw new AuthorizationException(){:Message = "Unauthorized portal access"}
      }

      if(documentDto.Name!=documentFile.Name){
        throw new FileNameMismatchException(DisplayKey.get("Document.upload.fileName.error", documentFile.Name))
      }

      if(documentDto.MimeType!=documentFile.ContentType) {
        throw new IllegalContentTypeException(DisplayKey.get("Document.upload.contentType.error", documentFile.ContentType))
      }

      if (!_fileUploadPlugin.canUploadContentType(documentFile)) {
        throw new IllegalContentTypeException("Cannot upload files of content type: " + documentFile.ContentType)
      }

      var policyPeriod = PolicyUtil.getLatestPolicyPeriodByPolicyNumber(documentDto.PolicyNumber)

      if (policyPeriod == null) {
        throw new BadIdentifierException("Bad policy number " + documentDto.PolicyNumber)
      }

      if (!_policyAuthCheck.hasAccess(policyPeriod)) {
        throw new NoAuthorityException()
      }


      final var res = Bundle.resolveInTransaction<Document>(\ bundle -> {
        policyPeriod = bundle.add(policyPeriod)
        final var doc = _documentPlugin.createDocumentMetadata(policyPeriod, documentDto)
        Plugins.get(IDocumentContentSource).addDocument(documentFile.InputStream, doc)
        return doc
      })

      /* Should do this outside the transactions as public ID is not set inside the bundle. */
      return _documentPlugin.getDocumentDetails(res)
    } finally {
      documentFile.InputStream.close()
    }
  }
}
The following codeblock contains the updated file content. Highlighted text indicates updates:
package edge.capabilities.policy.document

uses edge.jsonrpc.AbstractRpcHandler
uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.exception.JsonRpcSecurityException
uses org.apache.commons.fileupload2.core.FileItem
uses edge.di.annotations.InjectableNode
uses gw.api.webservice.exception.BadIdentifierException
uses edge.capabilities.policy.auth.IPolicyAccessPlugin
uses edge.security.authorization.exception.NoAuthorityException
uses edge.capabilities.policy.document.dto.PolicyDocumentDTO
uses gw.plugin.Plugins
uses gw.plugin.document.IDocumentContentSource
uses edge.capabilities.policy.document.dto.PolicyDocumentUploadDTO
uses edge.PlatformSupport.Bundle
uses edge.capabilities.document.IDocumentSessionPlugin
uses edge.security.authorization.exception.AuthorizationException
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.capabilities.policy.util.PolicyUtil
uses edge.security.fileupload.exception.IllegalContentTypeException
uses edge.security.fileupload.exception.FileNameMismatchException
uses gw.api.locale.DisplayKey
uses org.apache.tika.Tika
uses edge.security.fileupload.FileUploadUtil
uses edge.security.fileupload.DefaultFileUploadPlugin

/**
 * Handler of document uploads.
 * It is needed to work around "security through obscurity" in the authz service and its inability
 * to cope with many different access modes for the same URL. It is also a workaround for a third-party component
 * which is also inflexible and could not accommodate different transports.
 */
class PolicyDocumentUploadHandler extends AbstractRpcHandler {
  private var _policyAuthCheck : IPolicyAccessPlugin
  private var _documentPlugin : IPolicyDocumentsPlugin
  private var _documentSessionPlugin : IDocumentSessionPlugin
  private static final var TIKA = new Tika()
  static final var MIME_PNG = "image/png"
  static final var MIME_JPEG = "image/jpeg"
  static final var MIME_PDF = "application/pdf"

  @InjectableNode
  @Param("policyAuthCheck", "Policy access policy")
  @Param("documentPlugin", "Plugin used to access documents associated with the policy")
  @Param("documentSessionPlugin", "Document session management plugin")
  construct(policyAuthCheck : IPolicyAccessPlugin, documentPlugin : IPolicyDocumentsPlugin,
            documentSessionPlugin : IDocumentSessionPlugin) {
    this._policyAuthCheck = policyAuthCheck
    this._documentPlugin = documentPlugin
    this._documentSessionPlugin = documentSessionPlugin
  }

  @JsonRpcMethod
  @ApidocMethodDescription("Uploads a document. " +
      "NOTE: Given a list of file items, it expects that the element at [0] is the document metadata and at [1] is the item to be uploaded.")
  @ApidocAvailableSince("5.0")
  function upload(documentDto:PolicyDocumentUploadDTO, documentFile: FileItem) : PolicyDocumentDTO {
    try {
      try {
        _documentSessionPlugin.getSessionDocumentId(documentDto.SessionID)
      } catch (ex : JsonRpcSecurityException) {
        throw new AuthorizationException(){:Message = "Unauthorized portal access"}
      }

      if(documentDto.Name != documentFile.Name){
        throw new FileNameMismatchException(DisplayKey.get("Document.upload.fileName.error", documentFile.Name))
      }

      FileUploadUtil.assertSafeFileName(documentDto.Name)
      FileUploadUtil.assertSafeFileName(documentFile.Name)

      // Enforce max upload size
      FileUploadUtil.enforceMaxUploadSize(documentFile)

      // Read file bytes with limit
      var bytes = FileUploadUtil.readAllBytesAndCloseWithLimit(documentFile, FileUploadUtil.getMaxUploadBytes())

      // Detect and normalize MIME type
      var detectedType = FileUploadUtil.normalizeMimeType(TIKA.detect(bytes), documentFile.Name)

      // Enforce PDF name/content consistency
      FileUploadUtil.enforcePdfNameConsistency(documentFile.Name, detectedType, MIME_PDF)

      FileUploadUtil.assertExtensionMatchesType(documentFile.Name, detectedType)

      // Centralized allowed content type check
      if (!new DefaultFileUploadPlugin().canUploadContentType(detectedType)) {
        throw new IllegalContentTypeException("Cannot upload files of detected content type: " + detectedType)
      }

      // Validate PDF/image content
      FileUploadUtil.validateContent(detectedType, bytes, MIME_PDF, MIME_PNG, MIME_JPEG)

      var policyPeriod = PolicyUtil.getLatestPolicyPeriodByPolicyNumber(documentDto.PolicyNumber)
      if (policyPeriod == null) {
        throw new BadIdentifierException("Bad policy number " + documentDto.PolicyNumber)
      }

      if (!_policyAuthCheck.hasAccess(policyPeriod)) {
        throw new NoAuthorityException()
      }

      final var res = Bundle.resolveInTransaction<Document>(\ bundle -> {
        policyPeriod = bundle.add(policyPeriod)
        final var doc = _documentPlugin.createDocumentMetadata(policyPeriod, documentDto)
        Plugins.get(IDocumentContentSource).addDocument(new java.io.ByteArrayInputStream(bytes), doc)
        return doc
      })

      /* Should do this outside the transactions as public ID is not set inside the bundle. */
      return _documentPlugin.getDocumentDetails(res)
    } finally {
      documentFile.InputStream.close()
    }
  }
}

Changes to the service request file upload handler

Make these changes in gsrc/edge/capabilities/granite_on_cloud-servicerequest/document/ServiceRequestFileUploadHandler.gs:

The following codeblock contains the original file content. Highlighted text indicates content removed or changed in the updated file:
package edge.capabilities.servicerequest.document
              
uses edge.capabilities.servicerequest.document.dto.ServiceRequestDocumentDTO
uses edge.jsonrpc.exception.JsonRpcSecurityException
uses edge.security.fileupload.IFileUploadPlugin
uses edge.security.fileupload.exception.IllegalContentTypeException
uses org.apache.commons.fileupload2.core.FileItem
uses edge.jsonrpc.annotation.JsonRpcMethod
uses gw.webservice.cc.cc1000.vendormanagement.DocumentContent
uses gw.api.webservice.exception.BadIdentifierException
uses edge.capabilities.servicerequest.local.IServiceRequestRetrievalPlugin
uses edge.capabilities.document.IDocumentSessionPlugin
uses edge.security.authorization.Authorizer
uses edge.di.annotations.InjectableNode
uses edge.security.authorization.exception.AuthorizationException
uses edge.PlatformSupport.Bundle
uses gw.plugin.Plugins
uses gw.plugin.document.IDocumentContentSource
uses edge.jsonrpc.AbstractRpcHandler
uses edge.security.authorization.exception.NoAuthorityException
uses edge.security.fileupload.exception.FileNameMismatchException
  uses gw.api.locale.DisplayKey

class ServiceRequestFileUploadHandler extends AbstractRpcHandler {

  private var _documentPlugin : IServiceRequestDocumentPlugin
  private var _retrievalPlugin : IServiceRequestRetrievalPlugin
  private var _fileUploadPlugin: IFileUploadPlugin
  private var _documentSessionPlugin : IDocumentSessionPlugin
  private var _authz : Authorizer<Document>
  
  @InjectableNode
  @Param("documentPlugin", "Plugin used to deal with claim documents")
  @Param("documentSessionPlugin", "Plugin used to deal with document sessions")
  @Param("retrievalPlugin", "Plugin used to access service requests")
  @Param("fileUploadPugin", "Plugin used to validate file validity")
  public construct(documentPlugin : IServiceRequestDocumentPlugin,
  documentSessionPlugin : IDocumentSessionPlugin,
  docAuthz:Authorizer<Document>,
  retrievalPlugin : IServiceRequestRetrievalPlugin,
  fileUploadPlugin: IFileUploadPlugin) {
    this._documentPlugin = documentPlugin
    this._documentSessionPlugin = documentSessionPlugin
    this._retrievalPlugin = retrievalPlugin
    this._authz = docAuthz
    this._fileUploadPlugin = fileUploadPlugin
  }
  
  /**
  * Uploads a document
  *
  * Given a list of file items, it expects that the element
  * <ul>
  * <li> at [0] is the document metadata </li>
  * <li> at [1] is the item to be uploaded </li>
  * </ul>
  *
  * </br>
  * Throws -
  * <ul>
  * <li>IllegalArgumentException If id number associated with the document is null or empty</li>
  * <li>EntityNotFoundException If no service request statement is found</li>
  * <li>AuthorizationException If the portal user has no access to the service request statement</li>
  * </ul>
  *
  * @param list of file items
  * @return document metadata response
  */
  @JsonRpcMethod
  function upload(documentDto:ServiceRequestDocumentDTO, documentFile: FileItem) : ServiceRequestDocumentDTO {
    try {
      try {
        // validate document upload token
        _documentSessionPlugin.getSessionDocumentId(documentDto.SessionID)
      } catch (ex : JsonRpcSecurityException) {
        throw new AuthorizationException(){:Message = "Unauthorized portal access"}
      }
  
      if(documentDto.Name!=documentFile.Name){
        throw new FileNameMismatchException(DisplayKey.get("Document.upload.fileName.error", documentFile.Name))
      }
  
    if(documentDto.MimeType!=documentFile.ContentType) {
      throw new IllegalContentTypeException(DisplayKey.get("Document.upload.contentType.error", documentFile.ContentType))
    }
  
    if (!_fileUploadPlugin.canUploadContentType(documentFile)) {
      throw new IllegalContentTypeException("Cannot upload files of content type: " + documentFile.ContentType)
    }
    var serviceRequest = _retrievalPlugin.getServiceRequestFromPublicId(documentDto.ServiceRequestId)
  
    if (serviceRequest == null) {
      throw new BadIdentifierException("Bad service request id " + documentDto.ServiceRequestId)
    }
  
    final var res = Bundle.resolveInTransaction<Document>(\ bundle -> {
      serviceRequest = bundle.add(serviceRequest)
      var docContent = new DocumentContent(){
        :Content =  documentFile.get(),
        :MimeType = documentDto.MimeType
      };
      
      final var doc = _documentPlugin.createDocumentMetadata(bundle, serviceRequest, documentDto, docContent)
      if ( !_authz.canAccess(doc) ) {
        throw new NoAuthorityException()
      }
      Plugins.get(IDocumentContentSource).addDocument(documentFile.InputStream, doc)
      return doc
    })
    
    return  _documentPlugin.toDTO(res)
    } finally {
      documentFile.InputStream.close()
    }
  }
}
The following codeblock contains the updated file content. Highlighted text indicates updates:
package edge.capabilities.servicerequest.document
              
uses edge.capabilities.servicerequest.document.dto.ServiceRequestDocumentDTO
uses edge.jsonrpc.exception.JsonRpcSecurityException
uses edge.security.fileupload.IFileUploadPlugin
uses edge.security.fileupload.exception.IllegalContentTypeException
uses org.apache.commons.fileupload2.core.FileItem
uses edge.jsonrpc.annotation.JsonRpcMethod
uses gw.webservice.cc.cc1000.vendormanagement.DocumentContent
uses gw.api.webservice.exception.BadIdentifierException
uses edge.capabilities.servicerequest.local.IServiceRequestRetrievalPlugin
uses edge.capabilities.document.IDocumentSessionPlugin
uses edge.security.authorization.Authorizer
uses edge.di.annotations.InjectableNode
uses edge.security.authorization.exception.AuthorizationException
uses edge.PlatformSupport.Bundle
uses gw.plugin.Plugins
uses gw.plugin.document.IDocumentContentSource
uses edge.jsonrpc.AbstractRpcHandler
uses edge.security.authorization.exception.NoAuthorityException
uses edge.security.fileupload.FileUploadUtil
  uses org.apache.tika.Tika

class ServiceRequestFileUploadHandler extends AbstractRpcHandler {

  private var _documentPlugin : IServiceRequestDocumentPlugin
  private var _retrievalPlugin : IServiceRequestRetrievalPlugin
  private var _fileUploadPlugin: IFileUploadPlugin
  private var _documentSessionPlugin : IDocumentSessionPlugin
  private var _authz : Authorizer<Document>
    private static final var TIKA = new Tika()
    static final var MIME_PNG = "image/png"
    static final var MIME_JPEG = "image/jpeg"
    static final var MIME_PDF = "application/pdf"
  
  @InjectableNode
  @Param("documentPlugin", "Plugin used to deal with claim documents")
  @Param("documentSessionPlugin", "Plugin used to deal with document sessions")
  @Param("retrievalPlugin", "Plugin used to access service requests")
  @Param("fileUploadPugin", "Plugin used to validate file validity")
  public construct(documentPlugin : IServiceRequestDocumentPlugin,
  documentSessionPlugin : IDocumentSessionPlugin,
  docAuthz:Authorizer<Document>,
  retrievalPlugin : IServiceRequestRetrievalPlugin,
  fileUploadPlugin: IFileUploadPlugin) {
    this._documentPlugin = documentPlugin
    this._documentSessionPlugin = documentSessionPlugin
    this._retrievalPlugin = retrievalPlugin
    this._authz = docAuthz
    this._fileUploadPlugin = fileUploadPlugin
  }
  
  /**
  * Uploads a document
  *
  * Given a list of file items, it expects that the element
  * <ul>
  * <li> at [0] is the document metadata </li>
  * <li> at [1] is the item to be uploaded </li>
  * </ul>
  *
  * </br>
  * Throws -
  * <ul>
  * <li>IllegalArgumentException If id number associated with the document is null or empty</li>
  * <li>EntityNotFoundException If no service request statement is found</li>
  * <li>AuthorizationException If the portal user has no access to the service request statement</li>
  * </ul>
  *
  * @param list of file items
  * @return document metadata response
  */
  @JsonRpcMethod
  function upload(documentDto:ServiceRequestDocumentDTO, documentFile: FileItem) : ServiceRequestDocumentDTO {
    try {
      try {
      // validate document upload token
      _documentSessionPlugin.getSessionDocumentId(documentDto.SessionID)
    } catch (ex : JsonRpcSecurityException) {
      throw new AuthorizationException(){:Message = "Unauthorized portal access"}
    }
  
    // Reject names with control / bidi / non-printable chars
    FileUploadUtil.assertSafeFileName(documentDto.Name)
    FileUploadUtil.assertSafeFileName(documentFile.Name)
  
    FileUploadUtil.enforceMaxUploadSize(documentFile)
    var bytes = FileUploadUtil.readAllBytesAndCloseWithLimit(documentFile, FileUploadUtil.getMaxUploadBytes())
    var detectedType = FileUploadUtil.normalizeMimeType(TIKA.detect(bytes), documentFile.Name)
    FileUploadUtil.enforcePdfNameConsistency(documentFile.Name, detectedType, MIME_PDF)
    FileUploadUtil.assertExtensionMatchesType(documentFile.Name, detectedType)
  
    if (!_fileUploadPlugin.canUploadContentType(detectedType)) {
      throw new IllegalContentTypeException("Cannot upload files of content type: " + detectedType)
    }
  
    FileUploadUtil.validateContent(detectedType, bytes, MIME_PDF, MIME_PNG, MIME_JPEG)

    documentDto.MimeType = detectedType
  
  var serviceRequest = _retrievalPlugin.getServiceRequestFromPublicId(documentDto.ServiceRequestId)
  
  if (serviceRequest == null) {
  throw new BadIdentifierException("Bad service request id " + documentDto.ServiceRequestId)
  }
  
  final var res = Bundle.resolveInTransaction<Document>(\ bundle -> {
  serviceRequest = bundle.add(serviceRequest)
  var docContent = new DocumentContent(){
    :Content =  bytes,
    :MimeType = detectedType
  };
  
  final var doc = _documentPlugin.createDocumentMetadata(bundle, serviceRequest, documentDto, docContent)
  if ( !_authz.canAccess(doc) ) {
  throw new NoAuthorityException()
  }
    var uploadInputStream = new java.io.ByteArrayInputStream(bytes)
    try {
      Plugins.get(IDocumentContentSource).addDocument(uploadInputStream, doc)
    } finally {
      uploadInputStream.close()
    }
    return doc
  })
  
      return  _documentPlugin.toDTO(res)
    } finally {
      documentFile.InputStream.close()
    }
  }
}

New file upload utility

A new FileUploadUtil.gs file has been added:

File contents:
package edge.security.fileupload

uses java.io.ByteArrayInputStream
uses javax.imageio.ImageIO
uses java.io.IOException
uses javax.imageio.IIOException
uses java.nio.charset.StandardCharsets
uses java.net.URLEncoder
uses java.util.regex.Pattern
uses edge.security.fileupload.exception.IllegalContentTypeException
uses gw.api.util.Logger
uses org.apache.commons.fileupload2.core.FileItem
uses java.io.ByteArrayOutputStream
uses org.apache.tika.Tika

public class FileUploadUtil {
  private static final var MAX_UPLOAD_BYTES = 25 * 1024 * 1024 // 25 MB
  private static final var LOG = Logger.forCategory(FileUploadUtil.Type.QName)
  private static final var TIKA = new Tika()

  // Unicode control / bidi / zero-width / non-character ranges that break
  // Content-Disposition serialization and enable RTL-override filename spoofing.
  // Disallowed at upload; stripped at download for any pre-existing stored names.
  // U+061C       Arabic Letter Mark (ALM, bidi formatter)
  // U+200B-200F  zero-width and bidi marks (ZWSP, ZWNJ, ZWJ, LRM, RLM)
  // U+202A-202E  bidi embedding/override (incl. RLO U+202E)
  // U+2066-2069  bidi isolates (LRI, RLI, FSI, PDI -- Trojan Source vectors)
  // U+2028-2029  line/paragraph separators
  // U+FEFF       BOM / zero-width no-break space
  // U+FFFD       replacement character
  // Plus all C0 (0x00-0x1F) and C1 (0x7F-0x9F) control chars.
  private static final var DISALLOWED_NAME_CHAR : String =
      "[\\u0000-\\u001F\\u007F-\\u009F\\u061C\\u200B-\\u200F\\u202A-\\u202E\\u2066-\\u2069\\u2028\\u2029\\uFEFF\\uFFFD]"
  private static final var DISALLOWED_NAME_PATTERN : Pattern = Pattern.compile(DISALLOWED_NAME_CHAR)

  // XENG-2487: characters that enable Content-Disposition parameter injection
  // (`;`), quoted-string breakouts (`"`, `'`), or HTML metachars (`<`, `>`).
  // The download-path header builder already escapes these; reject at upload
  // as defense in depth.
  private static final var HEADER_UNSAFE_CHAR : String = "[\";'<>]"
  private static final var HEADER_UNSAFE_PATTERN : Pattern = Pattern.compile(HEADER_UNSAFE_CHAR)

  private static final var DEFAULT_DOWNLOAD_FILENAME = "document"

  public static function getMaxUploadBytes() : int {
    return MAX_UPLOAD_BYTES
  }

  public static function enforceMaxUploadSize(documentFile : FileItem) {
    var size : long
    try {
      size = documentFile.Size
    } catch (e : Exception) {
      // Fail closed: a missing/unreadable size indicates a malformed upload that
      // we should reject rather than wave through with an unbounded read.
      LOG.warn("Rejected upload: could not determine file size: " + e)
      throw new IllegalContentTypeException("Uploaded file size could not be determined.")
    }
    if (size > MAX_UPLOAD_BYTES) {
      throw new IllegalContentTypeException("Uploaded file exceeds maximum allowed size.")
    }
  }

  public static function readAllBytesAndCloseWithLimit(documentFile : FileItem, maxBytes : int) : byte[] {
    var bos = new ByteArrayOutputStream()
    var is = documentFile.InputStream
    var buffer = new byte[8192]
    var total = 0
    try {
      while (true) {
        var read = is.read(buffer)
        if (read < 0) {
          break
        }
        total += read
        if (total > maxBytes) {
          throw new IllegalContentTypeException("Uploaded file exceeds maximum allowed size.")
        }
        bos.write(buffer, 0, read)
      }
      var bytes = bos.toByteArray()
      if (bytes.length == 0) {
        throw new IllegalContentTypeException("Uploaded file is empty.")
      }
      return bytes
    } finally {
      try {
        is.close()
      } finally {
        bos.close()
      }
    }
  }

  public static function normalizeMimeType(mime : String, fileName : String) : String {
    var m = mime?.trim()?.toLowerCase()
    // Both application/zip and application/x-tika-ooxml are opaque OOXML containers
    // at this detection depth. Use the file extension to identify the specific type.
    if (m == "application/zip" or m == "application/x-tika-ooxml") {
      var mapped = mapZipByExtension(fileName)
      if (mapped != null) {
        return mapped
      }
      // No recognized extension — fall through to standard normalization.
      // canUploadContentType will reject the unknown type.
    }
    // application/x-tika-msoffice is Tika's generic supertype for legacy OLE2 binary
    // Office formats (.doc, .xls, .ppt, .msg). Palisades upgraded Tika returns this
    // instead of the specific canonical MIME types that pre-date OOXML. Use the file
    // extension to map back to the canonical type so the allowlist check can match.
    if (m == "application/x-tika-msoffice") {
      var mapped = mapOleByExtension(fileName)
      if (mapped != null) {
        return mapped
      }
      // No recognized extension — fall through; canUploadContentType will reject it.
    }
    // Tika sniffs CSV bytes as text/plain (the content is plain text), but the
    // file extension implies text/csv. Normalize so assertExtensionMatchesType
    // and the allowlist check both pass.
    if (m == "text/plain") {
      var n = fileName?.trim()?.toLowerCase()
      if (n != null and n.endsWith(".csv")) {
        return "text/csv"
      }
    }
    // video/quicktime is Tika's catch-all for the ISO Base Media File Format (ISOBMFF)
    // container family. MP4 files whose ftyp major brand is not one of the specific
    // brands Tika has magic bytes for (mp41, mp42) are detected as video/quicktime even
    // though they carry a .mp4 extension. Use the file extension to resolve to the
    // correct specific type, mirroring the zip→OOXML and msoffice→OLE patterns above.
    if (m == "video/quicktime") {
      var mapped = mapQuicktimeByExtension(fileName)
      if (mapped != null) {
        return mapped
      }
      // .mov / .qt / unrecognised extension — stays video/quicktime.
    }
    return normalizeMimeType(mime)
  }

  public static function normalizeMimeType(mime : String) : String {
    if (mime == null) {
      return null
    }
    var m = mime.trim().toLowerCase()
    if (m == "image/jpg") {
      return "image/jpeg"
    }
    if (m == "application/x-pdf") {
      return "application/pdf"
    }
    // RFC 4337 defines application/mp4 as the generic MP4 container type.
    // Tika may return this from extension-based detection while returning video/mp4
    // from bytes-based detection (or vice versa). Normalize to video/mp4 so the
    // assertExtensionMatchesType comparison and the allowlist check both succeed.
    if (m == "application/mp4") {
      return "video/mp4"
    }
    // Tika 3.x canonical type for WAV is audio/vnd.wave; audio/x-wav and audio/wave
    // are registered aliases. The allowlist entry is audio/wav (the IANA short form).
    // Normalize all three aliases so the allowlist check and extension comparison succeed.
    if (m == "audio/vnd.wave" or m == "audio/x-wav" or m == "audio/wave") {
      return "audio/wav"
    }
    // image/x-bmp and image/x-ms-bmp are legacy aliases for image/bmp (IANA-registered
    // since RFC 7903). Tika 3.x returns the canonical image/bmp but normalize the aliases
    // as defence-in-depth in case a client or older detector path returns one.
    if (m == "image/x-bmp" or m == "image/x-ms-bmp") {
      return "image/bmp"
    }
    // application/x-tika-ooxml and application/x-tika-msoffice are intentionally NOT
    // resolved here — a filename is required. Use normalizeMimeType(mime, fileName).
    return m
  }

  private static function mapZipByExtension(fileName : String) : String {
    if (fileName == null) {
      return null
    }
    var n = fileName.trim().toLowerCase()
    if (n.endsWith(".docx")) {
      return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
    }
    if (n.endsWith(".xlsx")) {
      return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    }
    if (n.endsWith(".pptx")) {
      return "application/vnd.openxmlformats-officedocument.presentationml.presentation"
    }
    return null
  }

  private static function mapOleByExtension(fileName : String) : String {
    if (fileName == null) {
      return null
    }
    var n = fileName.trim().toLowerCase()
    if (n.endsWith(".doc") or n.endsWith(".dot")) {
      return "application/msword"
    }
    if (n.endsWith(".xls") or n.endsWith(".xlt") or n.endsWith(".xlm")) {
      return "application/vnd.ms-excel"
    }
    if (n.endsWith(".ppt") or n.endsWith(".pot") or n.endsWith(".pps")) {
      return "application/vnd.ms-powerpoint"
    }
    if (n.endsWith(".msg")) {
      return "application/vnd.ms-outlook"
    }
    return null
  }

  // Tika detects the QuickTime/ISOBMFF container family as video/quicktime when the
  // file's ftyp major brand does not match one of the more specific magic-byte patterns
  // (e.g. ftypmp41/mp42 for video/mp4). Use the file extension to resolve to the
  // correct allowlisted subtype. .mov and .qt stay as video/quicktime (their own glob
  // entries in Tika); anything unrecognised returns null so the caller falls through.
  private static function mapQuicktimeByExtension(fileName : String) : String {
    if (fileName == null) {
      return null
    }
    var n = fileName.trim().toLowerCase()
    if (n.endsWith(".mp4") or n.endsWith(".mp4v") or n.endsWith(".mpg4")) {
      return "video/mp4"
    }
    // .mov and .qt are the native QuickTime extensions — keep video/quicktime.
    if (n.endsWith(".mov") or n.endsWith(".qt")) {
      return "video/quicktime"
    }
    return null
  }

  public static function isDecodableImage(bytes : byte[]) : boolean {
    try {
      return ImageIO.read(new ByteArrayInputStream(bytes)) != null
    } catch (e : IIOException) {
      LOG.debug("Image decode failed")
      return false
    } catch (e : IOException) {
      LOG.debug("Image decode failed")
      return false
    } catch (e : RuntimeException) {
      LOG.debug("Image decode failed")
      return false
    }
  }

  public static function passesPdfSanityCheck(bytes : byte[]) : boolean {
    if (bytes == null or bytes.length < 10) {
      return false
    }
    var headerLen = Math.min(8, bytes.length)
    var header = new String(bytes, 0, headerLen, StandardCharsets.ISO_8859_1)
    if (!header.startsWith("%PDF-")) {
      return false
    }
    var tailWindow = Math.min(64 * 1024, bytes.length)
    var tail = new String(bytes, bytes.length - tailWindow, tailWindow, StandardCharsets.ISO_8859_1)
    var eofIndex = tail.lastIndexOf("%%EOF")
    if (eofIndex < 0) {
      return false
    }
    var startxrefIndex = tail.lastIndexOf("startxref")
    if (startxrefIndex < 0 or startxrefIndex > eofIndex) {
      return false
    }
    var hasClassicXref = tail.contains("\nxref") or tail.contains("\rxref")
    var hasClassicTrailer = tail.contains("trailer")
    var hasXrefStreamMarker = tail.contains("/XRef")
    return (hasClassicXref and hasClassicTrailer) or hasXrefStreamMarker
  }

  public static function enforcePdfNameConsistency(fileName : String, detectedType : String, mimePdf : String) {
    if (fileName == null) {
      return
    }
    var n = fileName.trim().toLowerCase()
    var looksLikePdf = n.endsWith(".pdf") or n.contains(".pdf.")
    if (looksLikePdf and detectedType != mimePdf) {
      LOG.info("Rejected upload: filename indicates PDF but detected content is not PDF")
      throw new IllegalContentTypeException("File name indicates a PDF, but the uploaded file is not a valid PDF.")
    }
  }

  public static function validateContent(detectedType : String, bytes : byte[], mimePdf : String, mimePng : String, mimeJpeg : String) {
    switch (detectedType) {
      case mimePdf:
        if (!passesPdfSanityCheck(bytes)) {
          LOG.info("Rejected PDF upload: invalid content")
          throw new IllegalContentTypeException("The uploaded PDF appears to be invalid or corrupted. Please re-save and try again.")
        }
        break
      case mimePng:
      case mimeJpeg:
        if (!isDecodableImage(bytes)) {
          LOG.info("Rejected image upload: invalid content")
          throw new IllegalContentTypeException("The uploaded image appears to be invalid or corrupted. Please re-save and try again.")
        }
        break
      default:
        break
    }
  }

  // Reject file names containing Unicode control / bidi / zero-width / non-printable chars.
  // These characters break Content-Disposition serialization (causing browsers to render
  // attachments inline) and enable RTL-override extension spoofing.
  public static function assertSafeFileName(fileName : String) {
    if (fileName == null or fileName.length == 0) {
      throw new IllegalContentTypeException("File name is required.")
    }
    var matcher = DISALLOWED_NAME_PATTERN.matcher(fileName)
    if (matcher.find()) {
      // Report the first offending char as U+XXXX. Never echo the raw character
      // into the log or exception message — control/bidi chars can break log
      // parsers and enable log-injection.
      var codePoint = formatCodePoint(fileName.charAt(matcher.start()) as int)
      LOG.info("Rejected upload: file name contains disallowed character " + codePoint)
      throw new IllegalContentTypeException(
          "File name contains disallowed control or formatting character (" + codePoint + ").")
    }
    // XENG-2487: reject header-injection / quote-breakout / HTML metachars.
    var injMatcher = HEADER_UNSAFE_PATTERN.matcher(fileName)
    if (injMatcher.find()) {
      var ch = fileName.charAt(injMatcher.start())
      LOG.info("Rejected upload: file name contains disallowed character '" + ch + "'")
      throw new IllegalContentTypeException("File name must not contain '" + ch + "'.")
    }
    // Reject path separators so a malicious name cannot influence storage paths.
    if (fileName.contains("/") or fileName.contains("\\")) {
      LOG.info("Rejected upload: file name contains path separator")
      throw new IllegalContentTypeException("File name must not contain path separators.")
    }
  }

  private static function formatCodePoint(code : int) : String {
    var hex = Integer.toHexString(code).toUpperCase()
    var sb = new StringBuilder("U+")
    for (i in 0..|(4 - hex.length)) {
      sb.append("0")
    }
    sb.append(hex)
    return sb.toString()
  }

  // Strip disallowed characters. Used as defense-in-depth on the download path so any
  // names stored before the upload-side fix do not break the Content-Disposition header.
  public static function sanitizeFileName(fileName : String) : String {
    if (fileName == null) {
      return DEFAULT_DOWNLOAD_FILENAME
    }
    var stripped = DISALLOWED_NAME_PATTERN.matcher(fileName).replaceAll("")
        .replaceAll("[\\\\/]", "_")
        .trim()
    return stripped.length == 0 ? DEFAULT_DOWNLOAD_FILENAME : stripped
  }

  // Build an RFC 6266 Content-Disposition header value: an ASCII-only quoted fallback
  // plus a UTF-8 percent-encoded `filename*` for compliant clients. Always returns
  // `attachment; ...` so the browser never renders the document inline.
  public static function buildContentDisposition(fileName : String) : String {
    var safeName = sanitizeFileName(fileName)
    var asciiFallback = toAsciiFallback(safeName)
    var utf8Encoded = percentEncodeUtf8(safeName)
    return "attachment; filename=\"" + asciiFallback + "\"; filename*=UTF-8''" + utf8Encoded
  }

  // Replace anything outside printable ASCII (0x20-0x7E) with "_" and backslash-escape
  // any embedded quote or backslash so the value is safe inside the quoted-string per RFC 6266.
  private static function toAsciiFallback(name : String) : String {
    var sb = new StringBuilder()
    for (i in 0..|name.length) {
      var code = (name.charAt(i) as int)
      if (code < 0x20 or code > 0x7E) {
        sb.append("_")
      } else if (code == 0x22 or code == 0x5C) { // " or \
        sb.append("\\").append(name.charAt(i))
      } else {
        sb.append(name.charAt(i))
      }
    }
    var result = sb.toString()
    return result.length == 0 ? DEFAULT_DOWNLOAD_FILENAME : result
  }

  private static function percentEncodeUtf8(name : String) : String {
    try {
      // URLEncoder follows application/x-www-form-urlencoded rules, which differ
      // from RFC 5987 attr-char in two places: spaces become "+" (we convert to
      // %20) and "*" is left literal (we percent-encode to %2A so the filename*
      // value stays RFC-compliant for filenames like "budget*final.pdf").
      return URLEncoder.encode(name, StandardCharsets.UTF_8.name())
          .replace("+", "%20")
          .replace("*", "%2A")
    } catch (e : java.io.UnsupportedEncodingException) {
      LOG.debug("UTF-8 encoding unavailable when building Content-Disposition: " + e)
      return DEFAULT_DOWNLOAD_FILENAME
    }
  }

  // Cross-check the filename's implied content type (from extension via Tika)
  // against the bytes-detected type. Catches the `evil.exe`-with-image-bytes
  // pattern where bytes pass the allow-list but the filename extension would
  // not. Skips the check when the extension is unknown to Tika so files like
  // README (no extension) still go through.
  public static function assertExtensionMatchesType(fileName : String, detectedType : String) {
    if (fileName == null) {
      return
    }
    var extType : String
    try {
      extType = TIKA.detect(fileName)
    } catch (e : Exception) {
      LOG.debug("Could not infer type from filename '" + fileName + "': " + e)
      return
    }
    if (extType == null or extType == "application/octet-stream") {
      return
    }
    var normalizedExt = normalizeMimeType(extType, fileName)
    if (normalizedExt != detectedType) {
      LOG.info("Rejected upload: filename extension implies " + normalizedExt
          + " but content sniffs as " + detectedType)
      throw new IllegalContentTypeException(
          "File extension does not match detected content type.")
    }
  }
}

Changes to the claim document upload handler

The following sections show both the original code as well as the updated code. Each bold line indicates a change that needs to be made to addresses this fix. Make these changes in ClaimDocumentUploadHandler.gs:

The following codeblock contains the original file content. Bold text indicates content removed or changed in the updated file:
package edge.capabilities.claim.document


uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.exception.JsonRpcSecurityException
uses org.apache.commons.fileupload2.core.FileItem

uses edge.jsonrpc.AbstractRpcHandler
uses edge.di.annotations.InjectableNode
uses edge.capabilities.document.IDocumentSessionPlugin
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.capabilities.claim.local.IClaimRetrievalPlugin
uses gw.api.webservice.exception.BadIdentifierException
uses edge.PlatformSupport.Bundle
uses gw.plugin.Plugins
uses gw.plugin.document.IDocumentContentSource
uses edge.security.authorization.exception.AuthorizationException
uses edge.capabilities.claim.document.dto.ClaimDocumentUploadDTO
uses edge.capabilities.claim.document.dto.ClaimDocumentDTO
uses edge.capabilities.claim.document.IClaimDocumentPlugin
uses edge.security.fileupload.exception.IllegalContentTypeException
uses edge.security.fileupload.exception.FileNameMismatchException
uses gw.api.locale.DisplayKey
uses edge.security.fileupload.IFileUploadPlugin

/**
 * Handler of document uploads.
 * It is needed to work around "security through obscurity" in the authz service and its inability
 * to cope with many different access modes for the same URL. It is also a workaround for a third-party component
 * which is also inflexible and could not accommodate different transports.
 */
class ClaimDocumentUploadHandler extends AbstractRpcHandler {

  private var _documentPlugin : IClaimDocumentPlugin
  private var _documentSessionPlugin : IDocumentSessionPlugin
  private var _claimRetrievalPlugin : IClaimRetrievalPlugin
  private var _fileUploadPlugin : IFileUploadPlugin

  @InjectableNode
  @Param("documentPlugin", "Plugin used to deal with claim documents")
  @Param("documentSessionPlugin", "Plugin used to deal with document sessions")
  @Param("claimRetrievalPlugin", "Plugin used to access claim data")
  @Param("fileUploadPlugin", "Plugin used to verify file upload validity")
  construct(documentPlugin : IClaimDocumentPlugin,
            documentSessionPlugin : IDocumentSessionPlugin,
            claimRetrievalPlugin : IClaimRetrievalPlugin,
            fileUploadPlugin : IFileUploadPlugin) {
    this._documentPlugin = documentPlugin
    this._documentSessionPlugin = documentSessionPlugin
    this._claimRetrievalPlugin = claimRetrievalPlugin
    this._fileUploadPlugin = fileUploadPlugin
  }

  /**
   * Used to upload claim documents from the frontend.
   *
   * <dl>
   *  <dt>Calls:</dt>
   *  <dd><code>IDocumentSessionPlugin#isSessionValid(String)</code> - to check if the current document session token is valid in this context.</dd>
   *  <dd><code>IFileUploadPlugin#canUploadContentType(FileItem)</code> - to see if the filetype for the document is allowed by the DMS.</dd>
   *  <dd><code>IClaimRetrievalPlugin#getClaimByNumber(String)</code> - to retrieve the claim.</dd>
   *  <dd><code>IClaimDocumentPlugin#createDocumentMetadata(Bundle, Claim, ClaimDocumentUploadDTO)</code> - to convert DTO into metadata and attach it to the document.</dd>
   *  <dt>Throws:</dt>
   *  <dd><code>AuthorizationException</code> - If session is invalid</dd>
   *  <dd><code>IllegalContentTypeException</code> - If the filetype is not permitted by the DMS</dd>
   *  <dd><code>BadIdentifierException</code> - If the claim number does not exist</dd>
   * </dl>
   *
   * @param documentDto a DTO describing the metadata for the document
   * @param documentFile the document itself
   * @returns A claimDocumentDTO for display in the frontend
   * */
  @JsonRpcMethod
  @ApidocMethodDescription("Used to upload claim documents from the frontend.")
  @ApidocAvailableSince("5.0")
  function upload(documentDto:ClaimDocumentUploadDTO, documentFile: FileItem) : ClaimDocumentDTO {
    try {
      try {
        // validate document upload token
        _documentSessionPlugin.getSessionDocumentId(documentDto.SessionID)
      } catch ( ex : JsonRpcSecurityException) {
        throw new AuthorizationException(){:Message = "Unauthorized portal access"}
      }

      if(documentDto.Name!=documentFile.Name){
        throw new FileNameMismatchException(DisplayKey.get("Document.upload.fileName.error", documentFile.Name))
      }

      if(documentDto.MimeType!=documentFile.ContentType) {
        throw new IllegalContentTypeException(DisplayKey.get("Document.upload.contentType.error", documentFile.ContentType))
      }

      if (!_fileUploadPlugin.canUploadContentType(documentFile)) {
        throw new IllegalContentTypeException("Cannot upload files of content type: " + documentFile.ContentType)
      }

      var claim = _claimRetrievalPlugin.getClaimByNumber(documentDto.ClaimNumber)

      if (claim == null) {
        throw new BadIdentifierException("Bad claim number " + documentDto.ClaimNumber)
      }

      final var res = Bundle.resolveInTransaction<Document>(\ bundle -> {
        claim = bundle.add(claim)
        final var doc = _documentPlugin.createDocumentMetadata(bundle, claim, documentDto)
        Plugins.get(IDocumentContentSource).addDocument(documentFile.InputStream, doc)
        return doc
      })

      /* Should do this outside the transactions as public ID is not set inside the bundle. */
      return _documentPlugin.getDocumentDetails(res)
    } finally {
      documentFile.InputStream.close()
    }
  }
}
The following codeblock contains the updated file content. Highlighted text indicates updates:
package edge.capabilities.claim.document

uses edge.doc.ApidocAvailableSince
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.exception.JsonRpcSecurityException
uses org.apache.commons.fileupload2.core.FileItem
uses org.apache.tika.Tika
uses gw.api.util.Logger
uses edge.jsonrpc.AbstractRpcHandler
uses edge.di.annotations.InjectableNode
uses edge.capabilities.document.IDocumentSessionPlugin
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.capabilities.claim.local.IClaimRetrievalPlugin
uses gw.api.webservice.exception.BadIdentifierException
uses edge.PlatformSupport.Bundle
uses gw.plugin.Plugins
uses gw.plugin.document.IDocumentContentSource
uses edge.security.authorization.exception.AuthorizationException
uses edge.capabilities.claim.document.dto.ClaimDocumentUploadDTO
uses edge.capabilities.claim.document.dto.ClaimDocumentDTO
uses edge.security.fileupload.exception.IllegalContentTypeException
uses edge.security.fileupload.exception.FileNameMismatchException
uses gw.api.locale.DisplayKey
uses edge.security.fileupload.IFileUploadPlugin
uses edge.security.fileupload.FileUploadUtil

/**
 * Handler of document uploads.
 * It is needed to work around "security through obscurity" in the authz service and its inability
 * to cope with many different access modes for the same URL. It is also a workaround for a third-party component
 * which is also inflexible and could not accommodate different transports.
 */
class ClaimDocumentUploadHandler extends AbstractRpcHandler {

  private static final var LOG = Logger.forCategory(ClaimDocumentUploadHandler.Type.QName)
  private static final var TIKA = new Tika()
  static final var MIME_PNG = "image/png"
  static final var MIME_JPEG = "image/jpeg"
  static final var MIME_PDF = "application/pdf"

  private var _documentPlugin : IClaimDocumentPlugin
  private var _documentSessionPlugin : IDocumentSessionPlugin
  private var _claimRetrievalPlugin : IClaimRetrievalPlugin
  private var _fileUploadPlugin : IFileUploadPlugin

  @InjectableNode
  @Param("documentPlugin", "Plugin used to deal with claim documents")
  @Param("documentSessionPlugin", "Plugin used to deal with document sessions")
  @Param("claimRetrievalPlugin", "Plugin used to access claim data")
  @Param("fileUploadPlugin", "Plugin used to verify file upload validity")
  construct(documentPlugin : IClaimDocumentPlugin,
            documentSessionPlugin : IDocumentSessionPlugin,
            claimRetrievalPlugin : IClaimRetrievalPlugin,
            fileUploadPlugin : IFileUploadPlugin) {
    this._documentPlugin = documentPlugin
    this._documentSessionPlugin = documentSessionPlugin
    this._claimRetrievalPlugin = claimRetrievalPlugin
    this._fileUploadPlugin = fileUploadPlugin
  }

  /**
   * Used to upload claim documents from the frontend.
   *
   * <dl>
   *  <dt>Calls:</dt>
   *  <dd><code>IDocumentSessionPlugin#isSessionValid(String)</code> - to check if the current document session token is valid in this context.</dd>
   *  <dd><code>IFileUploadPlugin#canUploadContentType(FileItem)</code> - to see if the filetype for the document is allowed by the DMS.</dd>
   *  <dd><code>IClaimRetrievalPlugin#getClaimByNumber(String)</code> - to retrieve the claim.</dd>
   *  <dd><code>IClaimDocumentPlugin#createDocumentMetadata(Bundle, Claim, ClaimDocumentUploadDTO)</code> - to convert DTO into metadata and attach it to the document.</dd>
   *  <dt>Throws:</dt>
   *  <dd><code>AuthorizationException</code> - If session is invalid</dd>
   *  <dd><code>IllegalContentTypeException</code> - If the filetype is not permitted by the DMS</dd>
   *  <dd><code>BadIdentifierException</code> - If the claim number does not exist</dd>
   * </dl>
   *
   * @param documentDto a DTO describing the metadata for the document
   * @param documentFile the document itself
   * @returns A claimDocumentDTO for display in the frontend
   * */
  @JsonRpcMethod
  @ApidocMethodDescription("Used to upload claim documents from the frontend.")
  @ApidocAvailableSince("5.0")
  function upload(documentDto:ClaimDocumentUploadDTO, documentFile: FileItem) : ClaimDocumentDTO {
    try {
      try {
        // validate document upload token
        _documentSessionPlugin.getSessionDocumentId(documentDto.SessionID)
      } catch ( ex : JsonRpcSecurityException) {
        throw new AuthorizationException(){:Message = "Unauthorized portal access"}
      }

      if(documentDto.Name!=documentFile.Name){
        throw new FileNameMismatchException(DisplayKey.get("Document.upload.fileName.error", documentFile.Name))
      }
  
      FileUploadUtil.assertSafeFileName(documentDto.Name)
      FileUploadUtil.assertSafeFileName(documentFile.Name)

      FileUploadUtil.enforceMaxUploadSize(documentFile)

      var bytes = FileUploadUtil.readAllBytesAndCloseWithLimit(documentFile, FileUploadUtil.getMaxUploadBytes())

      var detectedType = FileUploadUtil.normalizeMimeType(TIKA.detect(bytes), documentFile.Name)

      FileUploadUtil.enforcePdfNameConsistency(documentFile.Name, detectedType, MIME_PDF)
      FileUploadUtil.assertExtensionMatchesType(documentFile.Name, detectedType)

      if (!_fileUploadPlugin.canUploadContentType(detectedType)) {
       throw new IllegalContentTypeException("Cannot upload files of detected content type: " + detectedType)
      }

      FileUploadUtil.validateContent(detectedType, bytes, MIME_PDF, MIME_PNG, MIME_JPEG)

      documentDto.MimeType = detectedType

      var claim = _claimRetrievalPlugin.getClaimByNumber(documentDto.ClaimNumber)
      if (claim == null) {
        throw new BadIdentifierException("Bad claim number " + documentDto.ClaimNumber)
      }

      final var res = Bundle.resolveInTransaction<Document>(\ bundle -> {
        claim = bundle.add(claim)
        final var doc = _documentPlugin.createDocumentMetadata(bundle, claim, documentDto)
        var uploadInputStream = new java.io.ByteArrayInputStream(bytes)
        try {
          Plugins.get(IDocumentContentSource).addDocument(uploadInputStream, doc)
        } finally {
          uploadInputStream.close()
        }
        return doc
      })

      return _documentPlugin.getDocumentDetails(res)
    } finally {
      documentFile.InputStream.close()
    }
  }
}

2025.07.3 release

This section lists all of the changes added in patch 3 of the 2025.07 release.

New Jutro version

Digital reference applications now use Jutro patch version 10.11.2-next-20260108222178. This brings enhanced security to the Jutro Design System.

2025.07.2 release

This section lists all of the changes added in patch 2 of the 2025.07 release.

New Jutro version

Digital reference applications now use Jutro patch version 10.11.1-next-20251113161946. This brings enhanced security to the Jutro Design System.

2025.07.1 release

This section lists all of the changes added in patch 1 of the 2025.07 release.

New Node 22 patch version

The supported Node version for Digital reference applications has been updated to patch version 22.15.1.

New file name utility

A new file, FileNameMismatchException.gs has been added. If the file is not present in your base configuration, create a new file at gsrc/edge/security/fileupload/exception/FileNameMismatchException.gs and add the following content:

FileNameMismatchException.gs
package edge.security.fileupload.exception

uses edge.exception.ApplicationErrorCode
uses edge.exception.ApplicationException

class FileNameMismatchException extends ApplicationException {

  construct() {
    super(ApplicationErrorCode.GW_SECURITY_ERROR)
  }

  construct(aMessage : String) {
    super(ApplicationErrorCode.GW_SECURITY_ERROR)
    Message = aMessage;
  }

}

2025.07 release

This section lists all of the changes added in the 2025.07 release.

New Jutro version

Digital reference applications now use Jutro 10.11.0.

Node 22 support

Node 22 is now supported. Support for Node 18 will continue until the December 2025 at which point it will no longer receive support.

Java 21 upgrade

InsuranceSuite products now use Java 21 and JDK 21. As a result, you must upgrade your version of Java to version 21.

Java EE 8 migrated to Jakarta EE 9

Java Enterprise Edition 8 apps must be migrated to Jakarta Enterprise Edition 9. Upgrade code that uses javax.* to instead use jakarta.*.

Edge API configuration changes

Guidewire has modified the configuration of certain Edge APIs in the 2025.07 release, so that particular fields are only returned to users who are authorized to access such data in those fields.

There are steps that you must take to continue returning that data, along with testing your Edge configuration. You can find these file changes in your own repository using the us_nise_2025_07_0-bc-cc-cm-pc-dg/14.0.10.2 tag and need to manually add the changes to your own files. The following files show the configuration changes:

Claim contacts changes in the ClaimCenter API

The returned third-party claim contacts have been restricted by applying a filter in following ClaimCenter API files:
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/auth/ClaimContactAuthorizer.gs
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/auth/GatewayClaimContactAuthorizer.gs

The following changes have been made in the ClaimContactAuthorizer.gs file:

Original file contents:
override function canAccess(contact: ClaimContact): boolean {
  var user = UserProvider.EffectiveUser

  if (!_supportedLobsPlugin.getSupportedLobs().contains(contact.Claim.Policy.PolicyType)) {
    return false
  }

  if (user.getTargets(AuthorityType.PRODUCER).HasElements) {
    // Producers can access any contact
    return true
  }

  if (user.getTargets(AuthorityType.POLICY).HasElements || user.getTargets(AuthorityType.ACCOUNT).HasElements) {
    // Policyholders can access any contact but vendors other than auto repair shops or auto towing agencies
    if (!ClaimContactUtil.isVendor(contact) || AuthorityPolicyCanView(contact)) {
      return true
    }
  }

  final var vendorAuths = user.getTargets(AuthorityType.VENDOR)
  if (vendorAuths.HasElements) {
    // Vendors can see any contact but vendors other than themselves
    if (!ClaimContactUtil.isVendor(contact) || vendorAuths.contains(contact.Contact.AddressBookUID)) {
      return true
    }
  }

  return false
}

private function AuthorityPolicyCanView(contact: ClaimContact): boolean {
    /*
     * Policyholders can access any contact but vendors except auto repair shops, auto towing agencies, or if vendor
     * type any of the following: a) CompanyVendor or b) PersonVendor or c.) a subtypes of the type of CompanyVendor or
     * PersonVendor
     */
    var isAuto = contact.AutoRepairShop != null || contact.AutoTowingAgcy != null
    var isCompanyVendor = contact.CompanyVendor != null && contact.Company.Subtype.Code == "CompanyVendor"
    var isPersonVendor = contact.PersonVendor != null
    return isAuto || isCompanyVendor || isPersonVendor
  }
}
Updated file contents:
override function canAccess(contact: ClaimContact): boolean {
    var user = UserProvider.EffectiveUser
    if (!_supportedLobsPlugin.getSupportedLobs().contains(contact.Claim.Policy.PolicyType)) {
      return false
    }
    if (user.getTargets(AuthorityType.PRODUCER).HasElements) {
      if (authorityProducerRolesAllowed(contact)) {
        return true
      }
    }
    if (user.getTargets(AuthorityType.POLICY).HasElements || user.getTargets(AuthorityType.ACCOUNT).HasElements) {
      // Policyholders can access allowed contact contact roles, no vendors other than auto repair shops or auto towing agencies
      if (authorityPolicyRolesAllowed(contact) || AuthorityPolicyCanView(contact)) {
        return true
      }
    }
    final var vendorAuths = user.getTargets(AuthorityType.VENDOR)
    if (vendorAuths.HasElements) {
      if (authorityVendorRolesAllowed(contact) || vendorAuths.contains(contact.Contact.AddressBookUID)) {
        return true
      }
    }
    return false
  }

  private function authorityProducerRolesAllowed(contact: ClaimContact): boolean {

    var allowedRoles = new ArrayList<ContactRole>()
    //Please add additional allowed roles here
    allowedRoles.add(ContactRole.get("insured"))
    allowedRoles.add(ContactRole.get("coveredparty"))
    allowedRoles.add(ContactRole.get("agent"))

    var hasRole = false
    var roles = contact.getRoles()
    for (role in roles) {
      if (allowedRoles.contains(role.getRole())) {
        hasRole = true
        break
      }
    }
    return hasRole
  }

  private function authorityVendorRolesAllowed(contact: ClaimContact): boolean {
    var allowedRoles = new ArrayList<ContactRole>()
    //Please add additional allowed roles here
    allowedRoles.add(ContactRole.get("insured"))
    var hasRole = false
    var roles = contact.getRoles()
    for (role in roles) {
      if (allowedRoles.contains(role.getRole())) {
        hasRole = true
        break
      }
    }
    return hasRole
  }

  private function authorityPolicyRolesAllowed(contact: ClaimContact): boolean {
    var allowedRoles = new ArrayList<ContactRole>()
    //Please add additional allowed roles here
    allowedRoles.add(ContactRole.get("insured"))
    allowedRoles.add(ContactRole.get("coveredparty"))
    var hasRole = false
    var roles = contact.getRoles()
    for (role in roles) {
      if (allowedRoles.contains(role.getRole())) {
        hasRole = true
        break
      }
    }
    return hasRole
  }

  private function AuthorityPolicyCanView(contact: ClaimContact): boolean {
    var isAuto = contact.AutoRepairShop != null || contact.AutoTowingAgcy != null
    return isAuto
  }
}

The following changes have been made in the GatewayClaimContactAuthorizer.gs file:

Original file contents:
override function canAccess(contact: ClaimContact): boolean {
  if (!_supportedLobsPlugin.getSupportedLobs().contains(contact.Claim.Policy.PolicyType)) {
    return false
  }

  return true
  }
}
Updated file contents:
override function canAccess(contact: ClaimContact): boolean {
    if (!_supportedLobsPlugin.getSupportedLobs().contains(contact.Claim.Policy.PolicyType)) {
      return false
    }

    return authorityProducerRolesAllowed(contact)
  }

  private function authorityProducerRolesAllowed(contact: ClaimContact): boolean {

    var allowedRoles = new ArrayList<ContactRole>()
    //Please add additional allowed roles here
    allowedRoles.add(ContactRole.get("insured"))
    allowedRoles.add(ContactRole.get("coveredparty"))
    allowedRoles.add(ContactRole.get("agent"))

    var hasRole = false
    var roles = contact.getRoles()
    for (role in roles) {
      if (allowedRoles.contains(role.getRole())) {
        hasRole = true
        break
      }
    }
    return hasRole
  }
}

Third-party exposure changes in the ClaimCenter API

The returned third-party exposures have been restricted by modifying how the toContactDTO method filters the exposures being returned in the following ClaimCenter API files:
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/details/DefaultClaimDetailPlugin.gs
Original file contents:
res.Exposures = claim.Exposures.map(\ e -> convertExposure(e))
Updated file contents:
res.Exposures = claim.Exposures.where(\e -> e.LossParty == TC_INSURED)
.map(\e -> convertExposure(e))

Third-party service request changes in the ClaimCenter API

The returned third-party service requests have been restricted by applying a filter in the following ClaimCenter API files:
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/auth/ServiceRequestAuthorizer.gs
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/auth/GatewayServiceRequestAuthorizer.gs

The following changes have been made in the ServiceRequestAuthorizer.gs file:

Original file contents:
var _userProvider : EffectiveUserProvider as readonly UserProvider

@ForAllGwNodes("claim")
@ForAllGwNodes("fnol")
@ForAllGwNodes("document")
construct(aUserProvider: EffectiveUserProvider) {
  _userProvider = aUserProvider
}

override function canAccess(serviceRequest: ServiceRequest): boolean {
  var user = UserProvider.EffectiveUser

  if (user.hasAuthority(AuthorityType.PRODUCER, serviceRequest.Claim.Policy.ProducerCode)) {
    return true

  }

  if (user.hasAuthority(AuthorityType.POLICY, serviceRequest.Claim.Policy.PolicyNumber)) {
    if (serviceRequest.Claim.Policy.PolicyType == PolicyType.TC_WORKERSCOMP) {
      if (serviceRequest.Claim.getContactByRole(ContactRole.TC_CLAIMANT) == serviceRequest.Instruction.CustomerContact) {
        return true
      }
    } else {
      if (serviceRequest.Claim.Policy.insured == serviceRequest.Instruction.CustomerContact) {
        return true
      }
    }
  } else if (hasAccessToAccount(user, serviceRequest.Claim.Policy.AccountNumber)) {
    return true
  }

  return false;
}
Updated file contents:
private var _claimContactAuthorizer : Authorizer<ClaimContact>

@ForAllGwNodes("claim")
@ForAllGwNodes("fnol")
@ForAllGwNodes("document")
construct(aUserProvider: EffectiveUserProvider, claimContactAuthorizer : Authorizer<ClaimContact>) {
  _userProvider = aUserProvider
  this._claimContactAuthorizer = claimContactAuthorizer
}

override function canAccess(serviceRequest: ServiceRequest): boolean {
  var user = UserProvider.EffectiveUser

  if (user.hasAuthority(AuthorityType.PRODUCER, serviceRequest.Claim.Policy.ProducerCode)) {
    if (_claimContactAuthorizer.canAccess(serviceRequest.Claim.getClaimContact(serviceRequest.Instruction.CustomerContact))) {
      return true
    }
  }

  if (user.hasAuthority(AuthorityType.POLICY, serviceRequest.Claim.Policy.PolicyNumber)) {
    if (serviceRequest.Claim.Policy.PolicyType == PolicyType.TC_WORKERSCOMP) {
      if (serviceRequest.Claim.getContactByRole(ContactRole.TC_CLAIMANT) == serviceRequest.Instruction.CustomerContact) {
        return true
      }
    } else {
      if (serviceRequest.Claim.Policy.insured == serviceRequest.Instruction.CustomerContact) {
        return true
      }
    }
  } else if (hasAccessToAccount(user, serviceRequest.Claim.Policy.AccountNumber)
      && serviceRequest.Claim.Policy.insured == serviceRequest.Instruction.CustomerContact) {
    return true
  }

  return false;
}

The following changes have been made in the GatewayServiceRequestAuthorizer.gs file:

Original file contents:
class GatewayServiceRequestAuthorizer implements Authorizer<ServiceRequest> {
  private var _userProvider : EffectiveUserProvider as readonly UserProvider
  private var _edgeAuthorizationPlugin : IEdgeAuthorizationPlugin

  @ForAllGwNodes("gatewayclaim")
  @ForAllGwNodes("gatewayfnol")
  @ForAllGwNodes("gatewaydocument")
  @Param("anEdgeAuthorizationPlugin", "Plugin to check authorization to access entities")
  construct(anEdgeAuthorizationPlugin : IEdgeAuthorizationPlugin) {
    this._edgeAuthorizationPlugin = anEdgeAuthorizationPlugin
  }

  override function canAccess(item : ServiceRequest) : boolean {
    return _edgeAuthorizationPlugin.isAuthorizedOnPolicy(item.Claim.Policy.PolicyNumber)

  }
}
Updated file contents:
class GatewayServiceRequestAuthorizer implements Authorizer<ServiceRequest> {
  private var _userProvider : EffectiveUserProvider as readonly UserProvider
  private var _edgeAuthorizationPlugin : IEdgeAuthorizationPlugin
  private var _claimContactAuthorizer : Authorizer<ClaimContact>

  @ForAllGwNodes("gatewayclaim")
  @ForAllGwNodes("gatewayfnol")
  @ForAllGwNodes("gatewaydocument")
  @Param("anEdgeAuthorizationPlugin", "Plugin to check authorization to access entities")
  construct(anEdgeAuthorizationPlugin : IEdgeAuthorizationPlugin, claimContactAuthorizer : Authorizer<ClaimContact>) {
    this._edgeAuthorizationPlugin = anEdgeAuthorizationPlugin
    this._claimContactAuthorizer = claimContactAuthorizer
  }

  override function canAccess(item : ServiceRequest) : boolean {
    return _edgeAuthorizationPlugin.isAuthorizedOnPolicy(item.Claim.Policy.PolicyNumber) &&
        _claimContactAuthorizer.canAccess(item.Claim.getClaimContact(item.Instruction.CustomerContactGw))
  }
}

Third-party check changes in the ClaimCenter API

The returned third-party checks have been restricted by applying a filter in the following ClaimCenter API files:
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/auth/CheckAuthorizer.gs
Original file contents:
class CheckAuthorizer implements Authorizer<Check> {
  /**
   * User provider for the check.
   */
  private var _userProvider : EffectiveUserProvider as readonly UserProvider

  @ForAllGwNodes
  construct(aUserProvider : EffectiveUserProvider) {
    this._userProvider = aUserProvider
  }

  override function canAccess(item : Check) : boolean {
    var isVendor = UserProvider.EffectiveUser.GrantedAuthorities.hasMatch(\authority -> authority.AuthorityType == AuthorityType.VENDOR)
    return !isVendor
  }
}
Updated file contents:
class CheckAuthorizer implements Authorizer<Check> {
  /**
   * User provider for the check.
   */
  private var _userProvider : EffectiveUserProvider as readonly UserProvider
  private var _claimContactAuthorizer : Authorizer<ClaimContact>

  @ForAllGwNodes
  construct(aUserProvider : EffectiveUserProvider, claimContactAuthorizer : Authorizer<ClaimContact>) {

    this._userProvider = aUserProvider
    this._claimContactAuthorizer = claimContactAuthorizer
  }

  override function canAccess(item : Check) : boolean {

    if (UserProvider.EffectiveUser.GrantedAuthorities.hasMatch(\authority -> authority.AuthorityType == AuthorityType.VENDOR)){
        return false
    }

    if (_claimContactAuthorizer.canAccess(item.FirstPayee.ClaimContact)) {
      return true
    }

    return false
  }
}

Policy-only vehicle and vehicle incident changes in the ClaimCenter API

The returned policy-only vehicles and vehicles incidents have been restricted by applying a filter in the following ClaimCenter API files:
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/lob/impl/lob-pa-personalauto/claimdetail/PAClaimDetailPlugin.gs
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/lob/impl/commonauto/DefaultVehicleIncidentPlugin.gs

The following changes have been made in the PAClaimDetailPlugin.gs file:

Original file contents:
override function toDTO(claim : Claim) : PAClaimDetailExtensionDTO {
    if (claim.Policy.PolicyType != PaTypeCode.PersonalAuto) {
      return null
    }

    final var res = new PAClaimDetailExtensionDTO()
    res.Vehicles = claim.Vehicles.map(\ v -> vehicleToDTO(claim, v))
    res.VehicleIncidents = claim.VehicleIncidentsOnly.map(\i -> _vehicleIncidentPlugin.toDTO(i))
    return res
  }
  
  override function exposureToDTO(exposure : Exposure) : PAClaimExposureExtensionDTO {
    if (exposure.VehicleIncident == null) {
      return null
    }
    
    final var res = new PAClaimExposureExtensionDTO()
    res.VehicleIncident = _vehicleIncidentPlugin.toDTO(exposure.VehicleIncident)
    return res
  }

  protected function vehicleToDTO(claim : Claim, v : Vehicle) : VehicleDTO {
    final var res = new VehicleDTO()
    VehicleUtil.fillBaseProperties(res, v)
    res.PolicyVehicle = VehicleUtil.isPolicyVehicle(claim, v)

    return res
  }
}
Updated file contents:
override function toDTO(claim : Claim) : PAClaimDetailExtensionDTO {
    if (claim.Policy.PolicyType != PaTypeCode.PersonalAuto) {
      return null
    }

    final var res = new PAClaimDetailExtensionDTO()
    res.Vehicles = claim.Vehicles.map(\ v -> vehicleToDTO(claim, v)).where(\elt -> elt != null)
    res.VehicleIncidents = claim.VehicleIncidentsOnly.map(\i -> _vehicleIncidentPlugin.toDTO(i)).where(\elt -> elt != null)
    return res
  }

  override function exposureToDTO(exposure : Exposure) : PAClaimExposureExtensionDTO {
    if (exposure.VehicleIncident == null) {
      return null
    }

    final var res = new PAClaimExposureExtensionDTO()
    res.VehicleIncident = _vehicleIncidentPlugin.toDTO(exposure.VehicleIncident)
    return res
  }

  protected function vehicleToDTO(claim : Claim, v : Vehicle) : VehicleDTO {
    var res = new VehicleDTO()
    if (VehicleUtil.isPolicyVehicle(claim, v)) {
      VehicleUtil.fillBaseProperties(res, v)
      res.PolicyVehicle = true
    } else {
      res = null
    }

    return res
  }
}

The following changes have been made in the DefaultVehicleIncidentPlugin file:

Original file contents:
override function toDTO(incident : VehicleIncident) : VehicleIncidentDTO {
    return Mapper.mapRef(incident,\ i -> {
      final var res = new VehicleIncidentDTO()
      fillBaseProperties(res, incident)
      res.Driver = Mapper.mapRef(
          incident.getClaimContactByRole(ContactRole.TC_DRIVER),
          \ c -> _claimContactPlugin.toContactDTO(c)
      )
      res.Passengers = _claimContactPlugin.toContactDTO(incident.getClaimContactsByRole(ContactRole.TC_PASSENGER))
      res.Vehicle = _mapper.mapRef(incident.Vehicle, \ v -> vehicleToDTO(incident.Claim, v))

      return res
    })
  }
Updated file contents:
override function toDTO(incident : VehicleIncident) : VehicleIncidentDTO {
    if (VehicleUtil.isPolicyVehicle(incident.Claim, incident.Vehicle)) {
      return Mapper.mapRef(incident,\ i -> {
        final var res = new VehicleIncidentDTO()
        fillBaseProperties(res, incident)
        res.Driver = Mapper.mapRef(
            incident.getClaimContactByRole(ContactRole.TC_DRIVER),
            \ c -> _claimContactPlugin.toContactDTO(c)
        )
        res.Passengers = _claimContactPlugin.toContactDTO(incident.getClaimContactsByRole(ContactRole.TC_PASSENGER))
        res.Vehicle = _mapper.mapRef(incident.Vehicle, \ v -> vehicleToDTO(incident.Claim, v))

        return res
      })
    } else {
      return null
    }
  }

TaxId changes in the ClaimCenter API

The TaxId has been masked in the ContactDTO in the following ClaimCenter API files:
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/contact/dto/ContactDTO.gs
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/contact/DefaultContactPlugin.gs

The following changes have been made in the ContactDTO.gs file:

Original file contents:
@JsonProperty @Pattern("^([0-9]{3}-[0-9]{2}-[0-9]{4}$|^[0-9]{2}-[0-9]{7})$")
var _taxID : String as TaxID
Updated file contents:
@JsonProperty @Pattern("^([0-9,*]{3}-[0-9,*]{2}-[0-9,*]{4}$|^[0-9,*]{2}-[0-9,*]{7})$")
var _taxID : String as TaxID

The following changes have been made in the DefaultContactPlugin.gs file:

Original file contents:
public static function updateBaseProperties(contact : Contact, dto : ContactDTO) {
  if ( contact typeis Person ) {
    final var person = contact as Person  // Carbon complains on Contact.FirstName
    person.FirstName = dto.FirstName
    person.LastName = dto.LastName
    person.Prefix = dto.Prefix
    person.Suffix = dto.Suffix
    person.MiddleName = dto.MiddleName
    person.CellPhone = dto.CellNumber
    person.DateOfBirth = dto.DateOfBirth
    person.Gender = dto.Gender
    ClaimContactPlatformHelper.updatePersonProperties(person, dto)
  }
  contact.Name = dto.ContactName
  contact.PrimaryPhone = dto.PrimaryPhoneType
  contact.HomePhone = dto.HomeNumber
  contact.WorkPhone = dto.WorkNumber
  contact.EmailAddress1 = dto.EmailAddress1
  contact.TaxID = dto.TaxID
}
public static function fillBaseProperties(dto : ContactDTO, contact : Contact) {
    dto.PublicID = contact.PublicID
    dto.AddressBookUID = contact.AddressBookUID
    dto.Subtype = contact.Subtype.Code
    dto.DisplayName = contact.DisplayName
    if(contact typeis CompanyVendor){
      dto.ContactType = "CompanyVendor"
    } else if(contact typeis PersonVendor){
      dto.ContactType = "PersonVendor"
    } else {
      dto.ContactType = contact.Subtype.Code
    }
    if(contact typeis Person){
      dto.FirstName = contact.FirstName
      dto.LastName = contact.LastName
      dto.Prefix = contact.Prefix
      dto.Suffix = contact.Suffix
      dto.MiddleName = contact.MiddleName
      dto.CellNumber = contact.CellPhone
      dto.DateOfBirth = contact.DateOfBirth
      dto.Gender = contact.Gender
      ClaimContactPlatformHelper.fillPersonProperties(dto, contact)
      if (contact typeis PersonVendor) {
        dto.PrimaryContactName = contact.PrimaryContact.DisplayName
      }
    } else if (contact typeis CompanyVendor) {
      dto.PrimaryContactName = contact.PrimaryContact.DisplayName
    }
    dto.ContactName = contact.Name
    dto.PrimaryPhoneType = contact.PrimaryPhone
    dto.HomeNumber = contact.HomePhone
    dto.WorkNumber = contact.WorkPhone
    dto.FaxNumber = contact.FaxPhone
    dto.EmailAddress1 = contact.EmailAddress1
    dto.TaxID = contact.TaxID
  }
}
Updated file contents:
public static function updateBaseProperties(contact : Contact, dto : ContactDTO) {
  if ( contact typeis Person ) {
    final var person = contact as Person  // Carbon complains on Contact.FirstName
    person.FirstName = dto.FirstName
    person.LastName = dto.LastName
    person.Prefix = dto.Prefix
    person.Suffix = dto.Suffix
    person.MiddleName = dto.MiddleName
    person.CellPhone = dto.CellNumber
    person.DateOfBirth = dto.DateOfBirth
    person.Gender = dto.Gender
    ClaimContactPlatformHelper.updatePersonProperties(person, dto)
  }
  contact.Name = dto.ContactName
  contact.PrimaryPhone = dto.PrimaryPhoneType
  contact.HomePhone = dto.HomeNumber
  contact.WorkPhone = dto.WorkNumber
  contact.EmailAddress1 = dto.EmailAddress1
  if (dto.TaxID != null && !dto.TaxID.equals(contact.maskTaxId(contact.TaxID))) {
        contact.TaxID = dto.TaxID
  }
}
public static function fillBaseProperties(dto : ContactDTO, contact : Contact) {
    dto.PublicID = contact.PublicID
    dto.AddressBookUID = contact.AddressBookUID
    dto.Subtype = contact.Subtype.Code
    dto.DisplayName = contact.DisplayName
    if(contact typeis CompanyVendor){
      dto.ContactType = "CompanyVendor"
    } else if(contact typeis PersonVendor){
      dto.ContactType = "PersonVendor"
    } else {
      dto.ContactType = contact.Subtype.Code
    }
    if(contact typeis Person){
      dto.FirstName = contact.FirstName
      dto.LastName = contact.LastName
      dto.Prefix = contact.Prefix
      dto.Suffix = contact.Suffix
      dto.MiddleName = contact.MiddleName
      dto.CellNumber = contact.CellPhone
      dto.DateOfBirth = contact.DateOfBirth
      dto.Gender = contact.Gender
      ClaimContactPlatformHelper.fillPersonProperties(dto, contact)
      if (contact typeis PersonVendor) {
        dto.PrimaryContactName = contact.PrimaryContact.DisplayName
      }
    } else if (contact typeis CompanyVendor) {
      dto.PrimaryContactName = contact.PrimaryContact.DisplayName
    }
    dto.ContactName = contact.Name
    dto.PrimaryPhoneType = contact.PrimaryPhone
    dto.HomeNumber = contact.HomePhone
    dto.WorkNumber = contact.WorkPhone
    dto.FaxNumber = contact.FaxPhone
    dto.EmailAddress1 = contact.EmailAddress1
    dto.TaxID = contact.maskTaxId(contact.TaxID)
  }
}

Vendor claim access changes in the ClaimCenter API

Vendor claim access has been restricted by removing vendor authority access in the following ClaimCenter API files:
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/auth/DocumentAuthorizer.gs
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/auth/PolicyAuthorizer.gs

The following changes have been made in the DocumentAuthorizer.gs file:

Original file contents:
override function canAccess(doc : Document) : boolean {
  if (!isPortalDefaultAccessible(doc) || !perm.Document.view(doc)) {
    return false
  }
  var user = UserProvider.EffectiveUser
  if (user.hasAuthority(AuthorityType.POLICY, doc.Claim.Policy.PolicyNumber)) {
    if (doc.Author == user.Username) {
      return true
    }
  }
  if (user.hasAuthority(AuthorityType.PRODUCER, doc.Claim.Policy.ProducerCode)) {
    return true
  }
  if (user.hasAuthority(AuthorityType.ACCOUNT, doc.Claim.Policy.AccountNumber)) {
    return true
  }
  final var vendorAuths = user.getTargets(AuthorityType.VENDOR)
  if (vendorAuths.HasElements &&
      doc.Claim.Contacts.where(\cc -> ClaimContactUtil.isVendor(cc))*.Contact*.AddressBookUID
          .hasMatch(\s -> vendorAuths.contains(s))) {
    return true
  }
  return false
}
Updated file contents:
override function canAccess(doc : Document) : boolean {
  if (!isPortalDefaultAccessible(doc) || !perm.Document.view(doc)) {
    return false
  }
  var user = UserProvider.EffectiveUser
  if (user.hasAuthority(AuthorityType.POLICY, doc.Claim.Policy.PolicyNumber)) {
    if (doc.Author == user.Username) {
      return true
    }
  }
  if (user.hasAuthority(AuthorityType.PRODUCER, doc.Claim.Policy.ProducerCode)) {
    return true
  }
  if (user.hasAuthority(AuthorityType.ACCOUNT, doc.Claim.Policy.AccountNumber)) {
    return true
  }
  return false
}

The following changes have been made in the PolicyAuthorizer.gs file:

Original file contents:
override function canAccess(policy : Policy) : boolean {
    if (!_supportedLobsPlugin.getSupportedLobs().contains(policy.PolicyType)) {
      return false
    }
    var user = UserProvider.EffectiveUser
    if (user.hasAuthority(AuthorityType.POLICY, policy.PolicyNumber)) {
      return true
    }
    if (user.hasAuthority(AuthorityType.PRODUCER, policy.ProducerCode)) {
      return true
    }
    if (user.hasAuthority(AuthorityType.ACCOUNT, policy.AccountNumber)) {
      return true
    }
    final var vendorAuths = user.getTargets(AuthorityType.VENDOR)
    if (vendorAuths.HasElements &&
        policy.Claim.Contacts.where(\cc -> ClaimContactUtil.isVendor(cc))*.Contact*.AddressBookUID
            .hasMatch(\s -> vendorAuths.contains(s))) {
      return true
    } 
    return false
  }
}
Updated file contents:
override function canAccess(policy : Policy) : boolean {
    if (!_supportedLobsPlugin.getSupportedLobs().contains(policy.PolicyType)) {
      return false
    }
    var user = UserProvider.EffectiveUser
    if (user.hasAuthority(AuthorityType.POLICY, policy.PolicyNumber)) {
      return true
    }
    if (user.hasAuthority(AuthorityType.PRODUCER, policy.ProducerCode)) {
      return true
    }
    if (user.hasAuthority(AuthorityType.ACCOUNT, policy.AccountNumber)) {
      return true
    }
    return false
  }
}

Financial details changes in the BillingCenter API

Credit card numbers and bank account details have been masked in the following BillingCenter API files:
  • BillingCenter/modules/configuration/gsrc/edge/aspects/validation/annotations/CreditCardNumber.gs
  • BillingCenter/modules/configuration/gsrc/edge/capabilities/billing/dto/AccountBankDetailsDTO.gs
  • BillingCenter/modules/configuration/gsrc/edge/capabilities/billing/PaymentInstrumentTokenHelper.gs

The following changes have been made in the CreditCardNumber.gs file:

Original file contents:
final var codeMap :  HashMap<String, int> = {
        CreditCardIssuer.TC_AMEX.Code -> 15,
        CreditCardIssuer.TC_MASTERCARD.Code -> 16,
        CreditCardIssuer.TC_DISCOVER.Code -> 16,
        CreditCardIssuer.TC_VISA.Code -> 16,
        CreditCardIssuer.TC_DINERSCLUB.Code -> 14
    }
  final var issuer = Expr.getProperty("CreditCardIssuer.Code", Validation.PARENT)
  final var requiredLength = Expr.call(ValidationFunctions#getFromMap(java.util.HashMap<Object,Object>,Object), {Expr.dtoConst(codeMap), issuer})
  override function getState(): Object[] {
    var creditCardNumberLength = Validation.strLength(Validation.VALUE)
    return {new ValidationRuleDTO(
        Expr.isNot(Expr.lessThan(creditCardNumberLength, requiredLength)),
            Expr.translate("Edge.Web.Api.Model.CreditCardNumber", {}))}
  }
}
Updated file contents:
final var codeMap :  HashMap<String, int> = {
     CreditCardIssuer.TC_AMEX.Code -> 15,
     CreditCardIssuer.TC_MASTERCARD.Code -> 16,
     CreditCardIssuer.TC_DISCOVER.Code -> 16,
     CreditCardIssuer.TC_VISA.Code -> 16,
     CreditCardIssuer.TC_DINERSCLUB.Code -> 14
}
final var issuer = Expr.getProperty("CreditCardIssuer.Code", Validation.PARENT)
final var requiredLength = Expr.call(ValidationFunctions#getFromMap(java.util.HashMap<Object,Object>,Object), {Expr.dtoConst(codeMap), issuer})
override function getState(): Object[] {
  var creditCardNumberLength = Validation.strLength(Validation.VALUE)
  return {new ValidationRuleDTO(
      Expr.all({
          Expr.isNot(Expr.lessThan(creditCardNumberLength, requiredLength)),
          Expr.isNot(Expr.call(ValidationFunctions#matchesPattern(String, String), {Expr.const("[0-9*-]+\\*+[0-9*-]+"), Validation.VALUE}))
      }),

      Expr.translate("Edge.Web.Api.Model.CreditCardNumber", {}))}
}

The following changes have been made in the AccountBankDetailsDTO.gs file:

Original file contents:
uses edge.aspects.validation.annotations.Required

class AccountBankDetailsDTO {
  @JsonProperty @Required
  var _bankABANumber : String as BankABANumber
  @JsonProperty @Required
  var _bankAccountNumber : String as BankAccountNumber
  @JsonProperty
  var _bankAccountType : typekey.BankAccountType as BankAccountType
  @JsonProperty @Required
  var _bankName : String as BankName
  construct(){} 
}
Updated file contents:
uses edge.aspects.validation.annotations.Required
uses edge.aspects.validation.annotations.Pattern

class AccountBankDetailsDTO {
  @JsonProperty @Required @Pattern("[^\\*]+")
  var _bankABANumber : String as BankABANumber
  @JsonProperty @Required @Pattern("[^\\*]+")
  var _bankAccountNumber : String as BankAccountNumber
  @JsonProperty
  var _bankAccountType : typekey.BankAccountType as BankAccountType
  @JsonProperty @Required  @Pattern("[^\\*]+")
  var _bankName : String as BankName
  construct(){}
}

The following changes have been made in the PaymentInstrumentTokenHelper.gs file:

Original file contents:
uses edge.capabilities.billing.dto.AccountCreditCardDTO
uses java.io.IOException
uses java.util.Date
uses java.io.EOFException
uses java.lang.IllegalArgumentException
public static function toToken(dto : PaymentInstrumentDTO) : String {
  final var tokenWriter = new DemoTokenWriter()
    .putString(MAGIC)
    .putInt(2)

  switch (dto.PaymentMethod) {
    case PaymentMethod.TC_CREDITCARD:
      tokenWriter
        .putInt(1)
        .putString(dto.CreditCardData.CreditCardNumber)
        .putString(dto.CreditCardData.CreditCardIssuer.Code)
        .putLong(dto.CreditCardData.CreditCardExpDate.Time)
      break
    case PaymentMethod.TC_WIRE:
      tokenWriter
        .putInt(2)
        .putString(dto.BankAccountData.BankABANumber)
        .putString(dto.BankAccountData.BankAccountNumber)
        .putString(dto.BankAccountData.BankAccountType.Code)
        .putString(dto.BankAccountData.BankName)
      break
    default:
      throw new IllegalArgumentException("Bad payment method " + dto.PaymentMethod)
  }

  return tokenWriter.getString()
final var version = tokenizer.nextInt()
switch(version) {
  case 1:
    result.PaymentMethod = PaymentMethod.TC_WIRE
    result.BankAccountData = new AccountBankDetailsDTO()
    result.BankAccountData.BankABANumber = tokenizer.nextString()
    result.BankAccountData.BankAccountNumber = tokenizer.nextString()
    result.BankAccountData.BankAccountType = BankAccountType.get(tokenizer.nextString())
    result.BankAccountData.BankName = tokenizer.nextString()
    if (tokenizer.nextToken() != null) {
      throw new IOException("Bad token format: Trailing data")
    }
    return result
  case 2:
    final var methodCode = tokenizer.nextInt()
    switch (methodCode) {
      case 1:
        result.PaymentMethod = PaymentMethod.TC_CREDITCARD
        result.CreditCardData = new AccountCreditCardDTO()
        result.CreditCardData.CreditCardNumber = tokenizer.nextString()
        result.CreditCardData.CreditCardIssuer = CreditCardIssuer.get(tokenizer.nextString())
        result.CreditCardData.CreditCardExpDate = new Date(tokenizer.nextLong())
        if (tokenizer.nextToken() != null) {
          throw new IOException("Bad token format: Trailing data")
        }
        return result
      case 2:
        result.PaymentMethod = PaymentMethod.TC_WIRE
        result.BankAccountData = new AccountBankDetailsDTO()
        result.BankAccountData.BankABANumber = tokenizer.nextString()
        result.BankAccountData.BankAccountNumber = tokenizer.nextString()
        result.BankAccountData.BankAccountType = BankAccountType.get(tokenizer.nextString())
        result.BankAccountData.BankName = tokenizer.nextString()
        if (tokenizer.nextToken() != null) {
          throw new IOException("Bad token format: Trailing data")
        }
Updated file contents:
uses edge.capabilities.billing.dto.AccountCreditCardDTO
uses java.io.IOException
uses java.util.Date
uses java.io.EOFException
uses java.lang.IllegalArgumentException
uses gw.api.privacy.EncryptionMaskExpressions
public static function toToken(dto : PaymentInstrumentDTO) : String {
  final var tokenWriter = new DemoTokenWriter()
      .putString(MAGIC)
      .putInt(2)

  switch (dto.PaymentMethod) {
    case PaymentMethod.TC_CREDITCARD:
        tokenWriter
            .putInt(1)
            .putString(dto.CreditCardData.CreditCardNumber)
            .putString(dto.CreditCardData.CreditCardIssuer.Code)
            .putLong(dto.CreditCardData.CreditCardExpDate.Time)
      break
    case PaymentMethod.TC_WIRE:
        tokenWriter
            .putInt(2)
            .putString(dto.BankAccountData.BankABANumber)
            .putString(dto.BankAccountData.BankAccountNumber)
            .putString(dto.BankAccountData.BankAccountType.Code)
            .putString(dto.BankAccountData.BankName)
      break
    default:
      throw new IllegalArgumentException("Bad payment method " + dto.PaymentMethod)
  }
  return tokenWriter.getString()
}
final var version = tokenizer.nextInt()
switch(version) {
  case 1:
    result.PaymentMethod = PaymentMethod.TC_WIRE
    result.BankAccountData = new AccountBankDetailsDTO()
    result.BankAccountData.BankABANumber = EncryptionMaskExpressions.maskString(tokenizer.nextString(), 14,3)
    result.BankAccountData.BankAccountNumber = EncryptionMaskExpressions.maskString(tokenizer.nextString(), 14, 3)
    result.BankAccountData.BankAccountType = BankAccountType.get(tokenizer.nextString())
    result.BankAccountData.BankName = EncryptionMaskExpressions.maskString(tokenizer.nextString(), 14, 3)
    if (tokenizer.nextToken() != null) {
      throw new IOException("Bad token format: Trailing data")
    }
    return result
  case 2:
    final var methodCode = tokenizer.nextInt()
    switch (methodCode) {
      case 1:
        result.PaymentMethod = PaymentMethod.TC_CREDITCARD
        result.CreditCardData = new AccountCreditCardDTO()
        var number = tokenizer.nextString()
        result.CreditCardData.CreditCardNumber = EncryptionMaskExpressions.maskString(number, 14,4)
        result.CreditCardData.CreditCardIssuer = CreditCardIssuer.get(tokenizer.nextString())
        result.CreditCardData.CreditCardExpDate = new Date(tokenizer.nextLong())
        if (tokenizer.nextToken() != null) {
          throw new IOException("Bad token format: Trailing data")
        }
        return result
      case 2:
        result.PaymentMethod = PaymentMethod.TC_WIRE
        result.BankAccountData = new AccountBankDetailsDTO()
        result.BankAccountData.BankABANumber = EncryptionMaskExpressions.maskString(tokenizer.nextString(), 14, 3)
        result.BankAccountData.BankAccountNumber = EncryptionMaskExpressions.maskString(tokenizer.nextString(), 14, 3)
        result.BankAccountData.BankAccountType = BankAccountType.get(tokenizer.nextString())
        result.BankAccountData.BankName = EncryptionMaskExpressions.maskString(tokenizer.nextString(), 14, 3)
        if (tokenizer.nextToken() != null) {
          throw new IOException("Bad token format: Trailing data")
        }

As a part of this update, to mask the corresponding information on the frontend, you need to add the following code snippet - "maskChar": "*", to the creditCardNumber object in a number of files:

  • DigitalPortals/applications/common/capabilities-react/gw-capability-policychange-common-react/pages/Payments/HOPAPaymentPage/PaymentPage.metadata.json5
  • DigitalPortals/applications/common/capabilities-react/gw-capability-policyrenewal-common-react/pages/RenewalPaymentPage/PaymentPage.metadata.json5
  • DigitalPortals/applications/common/capabilities-react/gw-capability-quoteandbind-common-react/pages/PaymentDetails/PaymentDetailsPage.metadata.json5
  • DigitalPortals/applications/common/modules-react/gw-components-platform-react/PaymentComponent/PaymentComponent.metadata.json5

Changes to draft claims in the ClaimCenter API

Additional roles and allowances for policyholders and agents have been added for draft claims in the following files:
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/lob/impl/personalauto/fnol/PAFnolPlugin.gs
  • ClaimCenter/modules/configuration/gsrc/edge/capabilities/claim/lob/impl/commonauto/DefaultVehicleIncidentPlugin.gs

The following changes have been made in the DefaultVehicleIncidentPlugin.gs file:

Original file contents:
override function toDTO(incident : VehicleIncident) : VehicleIncidentDTO {
  if (VehicleUtil.isPolicyVehicle(incident.Claim, incident.Vehicle)) {

    return Mapper.mapRef(incident,\ i -> {
      final var res = new VehicleIncidentDTO()
      fillBaseProperties(res, incident)
public static function updateBaseProperties(incident : VehicleIncident, dto : VehicleIncidentDTO) {
    incident.Description = dto.DamageDescription
    incident.PropertyDesc = dto.PropertyDamageDescription
    incident.VehicleOperable = dto.SafeToDrive
    incident.AirbagsDeployed = dto.AirbagsDeployed
    incident.EquipmentFailure = dto.EquipmentFailure
    incident.VehTowedInd = dto.VehicleTowed
    incident.RentalRequired = dto.RentalRequired
    incident.Collision = dto.Collision
    incident.CollisionPoint = dto.CollisionPoint
    incident.VehStolenInd = dto.Theft
    incident.Severity = dto.Severity

  }
}
Updated file contents:
override function toDTO(incident : VehicleIncident) : VehicleIncidentDTO {

  if ((incident.Claim.DraftClaim && incident.LossParty == TC_THIRD_PARTY) || VehicleUtil.isPolicyVehicle(incident.Claim, incident.Vehicle)) {
    return Mapper.mapRef(incident,\ i -> {
      final var res = new VehicleIncidentDTO()
      fillBaseProperties(res, incident)
public static function updateBaseProperties(incident : VehicleIncident, dto : VehicleIncidentDTO) {
    incident.Description = dto.DamageDescription
    incident.PropertyDesc = dto.PropertyDamageDescription
    incident.VehicleOperable = dto.SafeToDrive
    incident.AirbagsDeployed = dto.AirbagsDeployed
    incident.EquipmentFailure = dto.EquipmentFailure
    incident.VehTowedInd = dto.VehicleTowed
    incident.RentalRequired = dto.RentalRequired
    incident.Collision = dto.Collision
    incident.CollisionPoint = dto.CollisionPoint
    incident.VehStolenInd = dto.Theft
    incident.Severity = dto.Severity
    incident.LossParty = VehicleUtil.isPolicyVehicle(incident.Claim, incident.Vehicle) ? TC_INSURED : TC_THIRD_PARTY
  }
}

The following changes have been made in the PAFnolPlugin.gs file:

Original file contents:
override function toDTO(claim: Claim): PAFnolExtensionDTO {
  if (claim.Policy.PolicyType != PaTypeCode.PersonalAuto) {
    return null
  }
  
  final var res = new PAFnolExtensionDTO()
  res.Vehicles = Mapper.mapArray(claim.Policy.Vehicles*.Vehicle, \v -> VehicleUtil.toDTO(claim, v))
  res.VehicleIncidents = Mapper.mapArray(claim.VehicleIncidentsOnly, \v -> _incidentPlugin.toDTO(v))
  res.FixedPropertyIncident = Mapper.mapArray(claim.FixedPropertyIncidentsOnly, \e -> IncidentUtil.toDTO(e)).first()
  res.RepairOption = getRepairOptionDTO(claim)
  return res
}
Updated file contents:
override function toDTO(claim: Claim): PAFnolExtensionDTO {
  if (claim.Policy.PolicyType != PaTypeCode.PersonalAuto) {
    return null
  }
  
  final var res = new PAFnolExtensionDTO()
  res.Vehicles = Mapper.mapArray(claim.Policy.Vehicles*.Vehicle, \v -> VehicleUtil.toDTO(claim, v)).where(\elt -> elt != null)
  res.VehicleIncidents = Mapper.mapArray(claim.VehicleIncidentsOnly, \v -> _incidentPlugin.toDTO(v)).where(\elt -> elt != null)
  res.FixedPropertyIncident = Mapper.mapArray(claim.FixedPropertyIncidentsOnly, \e -> IncidentUtil.toDTO(e)).first()
  res.RepairOption = getRepairOptionDTO(claim)
  return res
}

Policy access changes in the PolicyCenter API

Policy access has been restricted in the following PolicyCenter API files:
  • PolicyCenter/modules/configuration/gsrc/edge/capabilities/policyjob/policydiff/GatewayPolicyDiffHandler.gs
  • PolicyCenter/modules/configuration/gsrc/edge/capabilities/policy/auth/DefaultPolicyAccessPlugin.gs
  • PolicyCenter/modules/configuration/gsrc/edge/capabilities/policyjob/policydiff/PolicyDiffHandler.gs

The following changes have been made in the GatewayPolicyDiffHandler.gs file:

Original file contents:
package edge.capabilities.policyjob.policydiff


uses edge.jsonrpc.AbstractRpcHandler
uses edge.di.annotations.InjectableNode
uses edge.capabilities.helpers.JobUtil
uses edge.doc.ApidocAvailableSince
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.PlatformSupport.Bundle
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser

class GatewayPolicyDiffHandler extends PolicyDiffHandler {

  @InjectableNode
  construct(aPolicyDiffPlugin : IPolicyDiffPlugin, aJobUtil : JobUtil) {
    super(aPolicyDiffPlugin, aJobUtil)
  }
Updated file contents:
package edge.capabilities.policyjob.policydiff

uses edge.capabilities.policy.auth.IPolicyAccessPlugin
uses edge.jsonrpc.AbstractRpcHandler
uses edge.di.annotations.InjectableNode
uses edge.capabilities.helpers.JobUtil
uses edge.doc.ApidocAvailableSince
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.PlatformSupport.Bundle
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser

class GatewayPolicyDiffHandler extends PolicyDiffHandler {

  @InjectableNode
  construct(aPolicyDiffPlugin : IPolicyDiffPlugin, aJobUtil : JobUtil,policyAccessPlugin: IPolicyAccessPlugin) {
    super(aPolicyDiffPlugin, aJobUtil,policyAccessPlugin)
  }

The following changes have been made in the DefaultPolicyAccessPlugin.gs file:

Original file contents:
override function hasAccess(policy : PolicyPeriod) : Boolean {
  var user = _userProvider.EffectiveUser
  /* Explicit access to a policy by number. */
  if (user.hasAuthority(AuthorityType.POLICY, policy.PolicyNumber)) {
    return true
  }
  
  /* Access to a parent entity. */
  if (hasAccess(policy.Policy)) {
    return true
  }

  return false

}
Updated file contents:
override function hasAccess(policy : PolicyPeriod) : Boolean {
  var user = _userProvider.EffectiveUser
  /* Explicit access to a policy by number. */
  if (user.hasAuthority(AuthorityType.POLICY, policy.PolicyNumber)) {
    return true
  }
  
  /* Access to a parent entity. */
  if (hasAccess(policy.Policy)) {
    return true
  }

  return perm.PolicyPeriod.view(policy)

}

The following changes have been made in the PolicyDiffHandler.gs file:

Original file content:
package edge.capabilities.policyjob.policydiff

uses edge.jsonrpc.AbstractRpcHandler
uses edge.di.annotations.InjectableNode
uses edge.capabilities.helpers.JobUtil
uses edge.doc.ApidocAvailableSince
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.PlatformSupport.Bundle
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser

class PolicyDiffHandler extends AbstractRpcHandler {
  
  private var _policyDiffPlugin : IPolicyDiffPlugin
  private var _jobUtil: JobUtil as JobUtil
  
  @InjectableNode
  construct(aPolicyDiffPlugin: IPolicyDiffPlugin, aJobUtil: JobUtil){
    this._policyDiffPlugin = aPolicyDiffPlugin
    this._jobUtil = aJobUtil
  }
  
  @ApidocAvailableSince("11.2")
  @JsonRpcMethod
  @ApidocMethodDescription("Returns the policy diff tree for a given job")
  function getPolicyDiffWithPrevious(jobNumber:String) : Object {
    var rootNode = Bundle.resolveInTransaction(\bundle -> {
      var policyPeriod = bundle.add(JobUtil.findJobByJobNumber(jobNumber).LatestPeriod)
      return new gw.api.tree.RowTreeRootNodeWrapper(gw.diff.tree.DiffTree.recalculateRootNodeForPolicyReview(policyPeriod)).rowTreeRootNode
  })
    return _policyDiffPlugin.toDTO(rootNode)
  }

  @ApidocAvailableSince("11.2")
  @JsonRpcMethod
  @ApidocMethodDescription("Returns the policy diff tree for given jobs")
  function compareJobs(jobNumber1:String, jobNumber2: String) : Object {
    var rootNode = Bundle.resolveInTransaction(\bundle -> {
      var policyPeriod1 = bundle.add(JobUtil.findJobByJobNumber(jobNumber1).LatestPeriod)
      var policyPeriod2 = bundle.add(JobUtil.findJobByJobNumber(jobNumber2).LatestPeriod)
      return new gw.api.tree.RowTreeRootNodeWrapper(
        gw.diff.tree.DiffTree.recalculateRootNode(policyPeriod1, policyPeriod2, DiffReason.TC_COMPAREJOBS)).rowTreeRootNode
    })
Updated file content:
package edge.capabilities.policyjob.policydiff

uses edge.capabilities.policy.auth.IPolicyAccessPlugin
uses edge.jsonrpc.AbstractRpcHandler
uses edge.di.annotations.InjectableNode
uses edge.capabilities.helpers.JobUtil
uses edge.doc.ApidocAvailableSince
uses edge.jsonrpc.annotation.JsonRpcMethod
uses edge.PlatformSupport.Bundle
uses edge.doc.ApidocMethodDescription
uses edge.jsonrpc.annotation.JsonRpcRunAsInternalGWUser
uses gw.api.webservice.exception.BadIdentifierException

class PolicyDiffHandler extends AbstractRpcHandler {
  private var _policyDiffPlugin : IPolicyDiffPlugin
  private var _jobUtil: JobUtil as JobUtil
  private var _policyAccessPlugin: IPolicyAccessPlugin
  
  @InjectableNode
  @Param("policyAccessPlugin", "Plugin used to validate policy access rules")
  construct(aPolicyDiffPlugin: IPolicyDiffPlugin, aJobUtil: JobUtil,policyAccessPlugin: IPolicyAccessPlugin){
    this._policyDiffPlugin = aPolicyDiffPlugin
    this._jobUtil = aJobUtil
    this._policyAccessPlugin = policyAccessPlugin
  }

  @ApidocAvailableSince("11.2")
  @JsonRpcMethod
  @ApidocMethodDescription("Returns the policy diff tree for a given job")
  function getPolicyDiffWithPrevious(jobNumber:String) : Object {

    var rootNode = Bundle.resolveInTransaction(\bundle -> {
      var policyPeriod = bundle.add(JobUtil.findJobByJobNumber(jobNumber).LatestPeriod)
    
      if (policyPeriod == null || !_policyAccessPlugin.hasAccess(policyPeriod)) {
        throw new BadIdentifierException("Bad job number " + jobNumber)
      }

    return new gw.api.tree.RowTreeRootNodeWrapper(gw.diff.tree.DiffTree.recalculateRootNodeForPolicyReview(policyPeriod)).rowTreeRootNode
    })
    return _policyDiffPlugin.toDTO(rootNode)
  }
  @ApidocAvailableSince("11.2")
  @JsonRpcMethod
  @ApidocMethodDescription("Returns the policy diff tree for given jobs")
  function compareJobs(jobNumber1:String, jobNumber2: String) : Object {
    var rootNode = Bundle.resolveInTransaction(\bundle -> {
      var policyPeriod1 = bundle.add(JobUtil.findJobByJobNumber(jobNumber1).LatestPeriod)
    
      if (policyPeriod1 == null || !_policyAccessPlugin.hasAccess(policyPeriod1)) {
        throw new BadIdentifierException("Bad job number " + jobNumber1)
      }
    
      var policyPeriod2 = bundle.add(JobUtil.findJobByJobNumber(jobNumber2).LatestPeriod)
    
      if (policyPeriod2 == null || !_policyAccessPlugin.hasAccess(policyPeriod2)) {
        throw new BadIdentifierException("Bad job number " + jobNumber2)
      }

    return new gw.api.tree.RowTreeRootNodeWrapper(
      gw.diff.tree.DiffTree.recalculateRootNode(policyPeriod1, policyPeriod2, DiffReason.TC_COMPAREJOBS)).rowTreeRootNode
    })

FEIN changes in the PolicyCenter API

The FEINOfficialID and _feinNumber have been masked in the following files:
  • PolicyCenter/modules/configuration/gsrc/edge/capabilities/gateway/contact/DefaultUnderwritingContactPlugin.gs
  • PolicyCenter/modules/configuration/gsrc/edge/capabilities/quote/submission/UnderwritingQuoteHandler.gs
  • PolicyCenter/modules/configuration/gsrc/edge/capabilities/quote/submission/base/DefaultUnderwritingBaseSubmissionPlugin.gs

The following changes have been made in the DefaultUnderwritingContactPlugin.gs file:

Original file content:
uses edge.capabilities.gateway.contact.dto.CompanyDTO
uses edge.capabilities.gateway.contact.dto.NamedInsuredCompanyDTO
uses edge.di.annotations.ForAllGwNodes
...
override function toDTO(aCompany: Company): NamedInsuredCompanyDTO {
    if (aCompany == null) {
      return null
    }
    final var dto = new NamedInsuredCompanyDTO()
    dto.FEIN = aCompany.FEINOfficialID
    dto.EmailAddress2 = aCompany.EmailAddress2
    dto.FaxNumber = aCompany.FaxPhone
    fillContact(aCompany, dto)
    return dto
  }

/**
   * Updates entity.Company from the values in the DTO
   *
   * @param aCompany entity.Company
   * @param dto      NamedInsuredCompanyDTO
   */

  override function updateContact(aCompany: Company, dto: NamedInsuredCompanyDTO) {
    if (aCompany == null) {
      return
    }
    aCompany.FEINOfficialID = dto.FEIN
    aCompany.EmailAddress2 = dto.EmailAddress2
    aCompany.FaxPhone = dto.FaxNumber
    super.updateContact(aCompany, dto)
  }
}
Updated file content:
uses edge.capabilities.gateway.contact.dto.CompanyDTO
uses edge.capabilities.gateway.contact.dto.NamedInsuredCompanyDTO
uses edge.di.annotations.ForAllGwNodes
uses gw.api.privacy.EncryptionMaskExpressions
...
override function toDTO(aCompany: Company): NamedInsuredCompanyDTO {
    if (aCompany == null) {
      return null
    }
    final var dto = new NamedInsuredCompanyDTO()
    dto.FEIN = EncryptionMaskExpressions.maskTaxId(aCompany.FEINOfficialID)
    dto.EmailAddress2 = aCompany.EmailAddress2
    dto.FaxNumber = aCompany.FaxPhone
    fillContact(aCompany, dto)
    return dto
  }

  /**
   * Updates entity.Company from the values in the DTO
   *
   * @param aCompany entity.Company
   * @param dto      NamedInsuredCompanyDTO
   */
  override function updateContact(aCompany: Company, dto: NamedInsuredCompanyDTO) {
    if (aCompany == null) {
      return
    }
    if (dto.FEIN != null && !dto.FEIN.equals(EncryptionMaskExpressions.maskTaxId(aCompany.FEINOfficialID))) {
      aCompany.FEINOfficialID = dto.FEIN
    }
    aCompany.EmailAddress2 = dto.EmailAddress2
    aCompany.FaxPhone = dto.FaxNumber
    super.updateContact(aCompany, dto)
  }
}

The following changes have been made in the UnderwritingQuoteHandler.gs file:

Original file content:
uses edge.security.authorization.IAuthorizerProviderPlugin
uses java.lang.IllegalArgumentException
uses java.lang.IllegalStateException
...
private function toDTOUpdateDraftSubmissionResponse(quoteDataDTO: QuoteDataDTO, submission: Submission): UpdateDraftSubmissionResponseDTO {
    return new UpdateDraftSubmissionResponseDTO(){
        :SessionUUID = quoteDataDTO.SessionUUID,
        :QuoteID = quoteDataDTO.QuoteID,
        :BaseData = quoteDataDTO.BaseData,
        :LobData = quoteDataDTO.LobData,
        :BindData = quoteDataDTO.BindData,
        :QuoteData = quoteDataDTO.QuoteData,
        :IsSubmitAgent = quoteDataDTO.IsSubmitAgent,
        :FeinNumber = submission.SelectedVersion.PrimaryNamedInsured.AccountContactRole.AccountContact.Contact.FEINOfficialID,
        :IndustryCode = _industryCodePlugin.toDTO(submission.SelectedVersion.PrimaryNamedInsured.IndustryCode)
        }
  }
Updated file content:
uses edge.security.authorization.IAuthorizerProviderPlugin
uses java.lang.IllegalArgumentException
uses java.lang.IllegalStateException
uses gw.api.privacy.EncryptionMaskExpressions
...
private function toDTOUpdateDraftSubmissionResponse(quoteDataDTO: QuoteDataDTO, submission: Submission): UpdateDraftSubmissionResponseDTO {
    return new UpdateDraftSubmissionResponseDTO(){
        :SessionUUID = quoteDataDTO.SessionUUID,
        :QuoteID = quoteDataDTO.QuoteID,
        :BaseData = quoteDataDTO.BaseData,
        :LobData = quoteDataDTO.LobData,
        :BindData = quoteDataDTO.BindData,
        :QuoteData = quoteDataDTO.QuoteData,
        :IsSubmitAgent = quoteDataDTO.IsSubmitAgent,
        :FeinNumber = EncryptionMaskExpressions.maskTaxId(submission.SelectedVersion.PrimaryNamedInsured.AccountContactRole.AccountContact.Contact.FEINOfficialID),
        :IndustryCode = _industryCodePlugin.toDTO(submission.SelectedVersion.PrimaryNamedInsured.IndustryCode)
        }
  }

The following changes have been made in the DefaultUnderwritingBaseSubmissionPlugin.gs file:

Original file content:
uses gw.api.util.DateUtil
uses java.lang.IllegalArgumentException
uses java.util.Date
...
override function updateDraftSubmission(selectedPeriod: PolicyPeriod, data: QuoteBaseDataDTO, updateDraftSubmissionRequestDTO: UpdateDraftSubmissionRequestDTO) {
    var allowedTermTypes = getTermTypes(selectedPeriod)
    if (not allowedTermTypes.contains(data.TermType)) {
      throw new IllegalArgumentException("Illeagl term ${data.TermType} for period")
    }
    selectedPeriod.TermType = data.TermType
    if (data.TermType == TermType.TC_OTHER and data.PeriodEndDate == null) {
      throw new IllegalArgumentException("Period End Date must be set when TermType is ${data.TermType}")
    }
    setPeriodDates(selectedPeriod, LocalDateUtil.toMidnightDate(data.PeriodStartDate), data.TermType)
    if (data.TermType == TermType.TC_OTHER) {
      selectedPeriod.PeriodEnd = LocalDateUtil.toMidnightDate(data.PeriodEndDate)
    }
    if (updateDraftSubmissionRequestDTO.FeinNumber.HasContent) {
      selectedPeriod.PrimaryNamedInsured.AccountContactRole.AccountContact.Contact.FEINOfficialID = updateDraftSubmissionRequestDTO.FeinNumber
    }
    if (updateDraftSubmissionRequestDTO.IndustryCode.Code.HasContent) {
      selectedPeriod.PrimaryNamedInsured.IndustryCode = _industryCodePlugin.findByCode(updateDraftSubmissionRequestDTO.IndustryCode.Code)
    }
  }
Updated file content:
uses gw.api.util.DateUtil
uses java.lang.IllegalArgumentException
uses java.util.Date
uses gw.api.privacy.EncryptionMaskExpressions
...
override function updateDraftSubmission(selectedPeriod: PolicyPeriod, data: QuoteBaseDataDTO, updateDraftSubmissionRequestDTO: UpdateDraftSubmissionRequestDTO) {
    var allowedTermTypes = getTermTypes(selectedPeriod)
    if (not allowedTermTypes.contains(data.TermType)) {
      throw new IllegalArgumentException("Illeagl term ${data.TermType} for period")
    }
    selectedPeriod.TermType = data.TermType
    if (data.TermType == TermType.TC_OTHER and data.PeriodEndDate == null) {
      throw new IllegalArgumentException("Period End Date must be set when TermType is ${data.TermType}")
    }

    setPeriodDates(selectedPeriod, LocalDateUtil.toMidnightDate(data.PeriodStartDate), data.TermType)
    if (data.TermType == TermType.TC_OTHER) {
      selectedPeriod.PeriodEnd = LocalDateUtil.toMidnightDate(data.PeriodEndDate)
    }
    if (updateDraftSubmissionRequestDTO.FeinNumber.HasContent) {
      var _feinNumber = updateDraftSubmissionRequestDTO.FeinNumber
      if (_feinNumber != null && !_feinNumber.equals(EncryptionMaskExpressions.maskTaxId(_feinNumber))) {
        selectedPeriod.PrimaryNamedInsured.AccountContactRole.AccountContact.Contact.FEINOfficialID = _feinNumber
      }
    }
    if (updateDraftSubmissionRequestDTO.IndustryCode.Code.HasContent) {
      selectedPeriod.PrimaryNamedInsured.IndustryCode = _industryCodePlugin.findByCode(updateDraftSubmissionRequestDTO.IndustryCode.Code)
    }
  }

Policyholder contact changes in the PolicyCenter API

Access to the policyholder account contacts have been restricted in the following files:
  • PolicyCenter/modules/configuration/gsrc/edge/capabilities/gateway/contact/ContactsHandler.gs
  • PolicyCenter/modules/configuration/gsrc/edge/capabilities/gateway/policy/PolicyHandler.gs
  • PolicyCenter/modules/configuration/gsrc/edge/capabilities/rewrite/GatewayRewriteHandler.gs
  • PolicyCenter/modules/configuration/gsrc/edge/capabilities/renewal/GatewayRenewalHandler.gs

The following changes have been made in the ContactsHandler.gs file:

    • Original file content:
      @JsonRpcMethod
        @ApidocMethodDescription("Retrieves the main account contacts.")
        @ApidocAvailableSince("6.0")
        public function getMainAccountContacts(
    • Updated file content:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Retrieves the main account contacts.")
        @ApidocAvailableSince("6.0")
        public function getMainAccountContacts(
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Retrieves related account contacts.")
        @ApidocAvailableSince("6.0")
        public function getRelatedAccountContacts(
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Retrieves related account contacts.")
        @ApidocAvailableSince("6.0")
        public function getRelatedAccountContacts(
    • Original file contents:
       @JsonRpcMethod
        @ApidocMethodDescription("Retrieves common account contacts.")
        @ApidocAvailableSince("6.0")
        public function getCommonAccountContacts(
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Retrieves common account contacts.")
        @ApidocAvailableSince("6.0")
        public function getCommonAccountContacts(
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Retrieves account contact details.")
        @ApidocAvailableSince("6.0")
        function getAccountContactDetails(publicId : String) : AccountContactDetailsDTO {
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Retrieves account contact details.")
        @ApidocAvailableSince("6.0")
        function getAccountContactDetails(publicId : String) : AccountContactDetailsDTO {
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Removes the Policy Contact From a Job, if Contact is not Primary Insured")
        @ApidocAvailableSince("7.0")
        public function removePolicyContact(jobId: String, contactId: String) {
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Removes the Policy Contact From a Job, if Contact is not Primary Insured")
        @ApidocAvailableSince("7.0")
        public function removePolicyContact(jobId: String, contactId: String) {
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Removes the Coverable Contact from the all Lines on the Job")
        @ApidocAvailableSince("7.0")
        public function removeCoverableContact(jobId: String, coverableId: String, contactId: String) {
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Removes the Coverable Contact from the all Lines on the Job")
        @ApidocAvailableSince("7.0")
        public function removeCoverableContact(jobId: String, coverableId: String, contactId: String) {
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Retrieves all Policy Contact Roles")
        @ApidocAvailableSince("7.0")
        public function getAllPolicyContactRoles(): AvailableContactRoleDTO[] {
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Retrieves all Policy Contact Roles")
        @ApidocAvailableSince("7.0")
        public function getAllPolicyContactRoles(): AvailableContactRoleDTO[] {
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Creates a new contact on the policy job")
        @ApidocAvailableSince("7.0")
        public function createPolicyContact(jobId: String, aPolicyContactDetailsDTO:
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Creates a new contact on the policy job")
        @ApidocAvailableSince("7.0")
        public function createPolicyContact(jobId: String, aPolicyContactDetailsDTO:
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Updates an existing contact on the policy job")
        @ApidocAvailableSince("7.0")
        public function updatePolicyContact(jobId: String, aPolicyContactDetailsDTO:
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Updates an existing contact on the policy job")
        @ApidocAvailableSince("7.0")
        public function updatePolicyContact(jobId: String, aPolicyContactDetailsDTO:
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Create an Account Contact on the account")
        @ApidocAvailableSince("7.0")
        public function createAccountContact(accountId: String, anAccountContactDetailsDTO:
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Create an Account Contact on the account")
        @ApidocAvailableSince("7.0")
        public function createAccountContact(accountId: String, anAccountContactDetailsDTO:
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Delete an Account Contact from the account")
        @ApidocAvailableSince("7.0")
        public function removeAccountContact(accountId: String, accountContactPublicId: String) {
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Delete an Account Contact from the account")
        @ApidocAvailableSince("7.0")
        public function removeAccountContact(accountId: String, accountContactPublicId: String) {
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Update an Account Contact from the account")
        @ApidocAvailableSince("7.0")
        public function updateAccountContact(accountId: String, anAccountContactDetailsDTO:
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Update an Account Contact from the account")
        @ApidocAvailableSince("7.0")
        public function updateAccountContact(accountId: String, anAccountContactDetailsDTO:
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Update an Account Contact from the account")
        @ApidocAvailableSince("7.0")
        public function createContact(accountContactDTO: AccountContactDTO) : AccountContactDTO {
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Update an Account Contact from the account")
        @ApidocAvailableSince("7.0")
        public function createContact(accountContactDTO: AccountContactDTO) : AccountContactDTO {
    • Original file contents:
      @JsonRpcMethod
        @ApidocMethodDescription("Retrieves all Account Contact Roles")
        @ApidocAvailableSince("7.0")
        public function getAllAccountContactRoles(): AvailableContactRoleDTO[] {
    • Updated file contents:
      @JsonRpcRunAsInternalGWUser
        @JsonRpcMethod
        @ApidocMethodDescription("Retrieves all Account Contact Roles")
        @ApidocAvailableSince("7.0")
        public function getAllAccountContactRoles(): AvailableContactRoleDTO[] {

The following changes have been made in the PolicyHandler.gs file:

Original file contents:
@JsonRpcMethod
  @ApidocMethodDescription("Returns the job type of a policy period")
  @ApidocAvailableSince("8.0")
  function getPolicyPeriodJobType(policyNumber: String, termNumber: int): String {
Updated file content:
@JsonRpcRunAsInternalGWUser
  @JsonRpcMethod
  @ApidocMethodDescription("Returns the job type of a policy period")
  @ApidocAvailableSince("8.0")
  function getPolicyPeriodJobType(policyNumber: String, termNumber: int): String {

The following changes have been made in the GatewayRewriteHandler.gs file:

Original file content:
@JsonRpcMethod
  @ApidocMethodDescription("Checks if a job has been quoted.")
  @ApidocAvailableSince("8.0")
  function isQuoted(jobNumber: String) : Boolean {
Updated file content:
@JsonRpcRunAsInternalGWUser
  @JsonRpcMethod
  @ApidocMethodDescription("Checks if a job has been quoted.")
  @ApidocAvailableSince("8.0")
  function isQuoted(jobNumber: String) : Boolean {

The following changes have been made in the GatewayRenewalHandler.gs file:

Original file content:
@JsonRpcMethod
  @ApidocMethodDescription("Checks if a job has been quoted.")
  @ApidocAvailableSince("7.0")
  function isQuoted(renewalNumber:String) : Boolean {
Updated file content:
@JsonRpcRunAsInternalGWUser
  @JsonRpcMethod
  @ApidocMethodDescription("Checks if a job has been quoted.")
  @ApidocAvailableSince("7.0")
  function isQuoted(renewalNumber:String) : Boolean {

Fixed undefined values for the frontend ClaimsDetails file

The following changes have been made in the DigitalPortals/applications/common/capabilities-react/gw-capability-claim-react/pages/ClaimDetails/ClaimDetails.jsx file:

Original file contents:
getReportedByName = (claimDetailsData, translator) => {
  if (claimDetailsData.claimReporter.reportedBy.displayName) {
    return claimDetailsData.claimReporter.reportedBy.displayName;
  }
return translator(messages.unknown);
Updated file contents:
  getReportedByName = (claimDetailsData, translator) => {
  if (claimDetailsData.claimReporter.reportedBy?.displayName) {
    return claimDetailsData.claimReporter.reportedBy.displayName;
  }
return translator(messages.unknown);

For more information about authorizers, see the CustomerEngage Account Management Development Guide. For more information about Guidewire's recommendations for personally identifiable information (PII), see this Guidewire Cloud Standards documentation page.