!******************************************************************************!
! Tests attribute based authorization over an SSL connection
!
! This requires:
! 1) access to a REST service that provides a JSON response with user attributes
! 2) An SSLUtilities.properties/SSLUtiltiesAlt.properties file that
!    specifies the location of the truststore, format of REST service
!    url, and "paths" to the attributes within the JSON response
! 3) the user to have access to a certificate that is trusted by
!    the REST service
! Note: This may not work under Java 9.
!
! @bug      NXM-3034/NXM-3084 PKI/SSL and Attribute Based Authorization Support
! @author:  Jeanette McCall
! @since    NeXtMidas 3.7.2, but with significant changes in 3.7.3
!******************************************************************************!
startmacro
  switch INTERACTIVE interAct    GET DEF=TRUE  SDEF=TRUE  ! for normal interactive test(s)
  switch BASELINE    baseline    GET DEF=FALSE SDEF=TRUE  ! for defects or more obscure test case(s)
  switch DEBUG       debug       GET DEF=FALSE SDEF=TRUE  ! to bump priority level of SSLUtiltities log messages
  switch INSECURE    insecure    GET DEF=FALSE SDEF=TRUE  ! run in insecure mode (trust everything, but provide cert)
  switch ANYHOSTNAME anyhostname GET DEF=FALSE SDEF=TRUE  ! run regular ssl, except do not do host name verification
  switch ALTTESTENV  alttestenv  GET DEF=FALSE SDEF=TRUE

  if interAct then
    res propsFileDefault "SSLUtilities.properties"
    res propsFileAlt "SSLUtilitiesAlt.properties"
    if alttestenv then
      if propsFileAlt fexists then
        res propsFile propsFileAlt
      else
        warn "^reg.main.name: Running in alternate test environment but SSLUtilitiesAlt.properties file not found, defaulting to SSLUtilities.properties"
        res propsFile propsFileDefault
      endif
    endif

    invoke levelCurrent nxm.sys.lib.SSLUtilities.getCertInfoLogLevel()
    invoke levelConfig  java.util.logging.Level.CONFIG
    if DEBUG then
      invoke ,, nxm.sys.lib.SSLUtilities.setCertInfoLogLevel(levelConfig)
    endif
    env get HOME HOME
    ! default test certificate for private key
    res default_cert_loc "^{HOME}nxmcerts/"
    res pki_private_cert_path "^{default_cert_loc}client_multiple.p12"

    res mainTestCompleted false  ! main test has been run

    res inst "This test can only be run if the following are available:\n&
             1) Access to a REST service that provides a JSON response.\n&
             2) An ^{propsFile} file (in CLASSPATH or READ AUX) that &
                specifies the location of the truststore, format of REST\n   &
                service url, and "paths" to the attributes within the JSON response.\n&
             3) A PKI certificate that is trusted by the REST Service\n\n&
             If you are encountering this test during release testing, please &
             see the Confluence page describing SSLUtilties testing set-up. &
             If you do not have access to the above, skip this test.\n&
             4) Once SSL authorization is complete and successful, &
             please run the secure jar signing test. This will take a few minutes.\n&
             ------------------------------------------------------------------------\n&
             Full release testing:\n\n&
             1) This test should be run with each of the certificate types &
             listed in the drop down choice menu.\n   &
             (select certificate and then click 'Run this test')\n&
             2) If prompted, enter the password and/or select the appropriate certificate.\n   &
                a) expired certificates should not show\n   &
                b) revoked may show (and work) if there is not revokation server\n&
             3) You will need exit/reenter the NeXtMidas shell to test each certificate selection.\n\n&
             Check the terminal for the test results.  Click PASS if there are no failures &
             for any of the certificate types"


    pipe on
      panel/msgid=MAIN/setup/controls=gc

      gcontrol label macro "MACRO"
      gcontrol menu main "Exit with Status = " "PASS,FAIL" /tleft
      set gc.main.itemcolors {PASS=GREEN,FAIL=RED}

      gcontrol text idtext "Instructions" "^inst" 50 23 /title

      ! Note: Keep "Use SSLUtilities.properties/SSLUtilitiesAlt.properties file value first in list for easy identification"
      gcontrol CHOICE cert & 
               "Select Private Key Certificate (assumed to be in ^{HOME}nxmcerts) or enter certificate path" &
               "use ^{propsFile} value,client_multiple.p12,client_multiple.pfx,&
               client_multiple_key_first.pem,client_multiple_cert_first.pem,client_multiple.jks" &
               "client_multiple.p12" /input /cs /ttop /INPUTBORDER=PERM
      gcontrol line
      gcontrol button run_test "" "Run main test"
      gcontrol button run_jarsign "" "Run jar signing test"
    pipe off

    ! restore logging level
    invoke ,, nxm.sys.lib.SSLUtilities.setCertInfoLogLevel(levelCurrent)
  endif

  %test_checkauthorization_jsonresponse
endmacro

!******************************************************************************!
! Main process message for this macro. Should be right after the ENDMACRO.
!******************************************************************************!
procedure processMessage m:msg
  if msg.name eqs "MAIN" then
    assert/text="^reg.main.name: User indicated tests" msg.data eqss "PASS"
    pipe stop
  elseif msg.name eqs "RUN_TEST" then
    call runTests
    res mainTestComplete true
  elseif msg.name eqs "RUN_JARSIGN" then
    invoke sslInitialized nxm.sys.lib.SSLUtilities.isInitDone()
    if sslInitialized then
      call testJarSigning
    else
      warn "SSL needs to be initialized before the jar signing test - please run the main test first"
    endif
  elseif msg.name eqs "CERT" then
    if ^{msg.info} EQ 0 then
      ! user input path
      res pki_private_cert_path ^{msg.data}
      info "Your selected private key path was ^pki_private_cert_path"
    elseif ^{msg.info} EQ 1 then
      ! Use properties file value
      res pki_private_cert_path ""
      info "The private key path specified in ^{propsFile} will be used"
    else
      res pki_private_cert_path "^{default_cert_loc}^{msg.data}"
      info "Your selected private key path was ^pki_private_cert_path"
    endif
  else
    say "Unhandled message name: ^msg.name from fid: ^msg.fid data: ^msg.data"
  endif
return

!******************************************************************************!
! If an unexpected exception is caught, mark test as failed, unless it is expected
!******************************************************************************!
procedure processException m:emsg
  invoke cause emsg.data.getCause()
  ! Interested in root cause of Exception (may be wrapped)
  if cause ISNULL then
    res causeStr emsg.data.toString()
  else
    res causeStr cause.toString()
  endif
  ! Check if Exception is expected at this point in the test
  if expectedException REXISTS then
    ! If so, make sure it is the correct one
    assert/text="^reg.main.name: Expected ERROR is: ^{expectedException}" causeStr EQSS ^expectedException
    if "^expectedException" eqs "java.lang.IllegalStateException: Initialization already complete" then
      res emsg.data
    endif
    remove expectedException
  else
    ! Check for a common failure scenario (usually caused by a cert not signed by a trusted authority)
    ! (give a more detailed message and skip remaining authorization check tests we get this failure)
    if causeStr EQSS "java.net.SocketException: Broken pipe" then
      assert/text="^reg.main.name: Exception is probably due to a untrusted certificate: ^{emsg.data}" "FALSE" isTrue
      res connectingWithCertFailed true
    else 
      assert/text="^reg.main.name: Caught unexpected exception ^{emsg.data}" "FALSE" isTrue
      if "Trace" SUBS ENV.DEBUG then invoke ,, emsg.data.printStackTrace()
    endif
  endif
return

!******************************************************************************!
! If an unexpected exception is caught, mark test as failed, unless it is expected
!******************************************************************************!
procedure runTests

  invoke sslInitialized nxm.sys.lib.SSLUtilities.isInitDone()
  if sslInitialized then
    warn "Skipping SSL Initialization tests - initialization already complete"
    warn "If SSL initialization was done with a certificate not trusted by the REST server, the remaining tests will fail"
  else
    ! Test invalid calling of authorization commands/methods before SSL 
    ! initialization is complete
    set expectedException "java.lang.IllegalStateException: Not Initialized"
    call testHasOneOfAttributes false
    set expectedException       "java.lang.IllegalStateException: Not Initialized"
    call testInProjectGroup     false
    ! Now initialize / test initializing SSLContext
    call testInitSSL            true
  endif

  if sslInitialized then
    ! Test validation based on various attributes
    ! Note: normally the USERDN returned from sslconfig would be used to
    ! query the REST service, but in these methods known fake UIDs are used
    ! (though DNs are preferred for queries, aliases / UIDs are allowed)
    res connectingWithCertFailed false  !default
    call testHasMultipleAttributes true
    if connectingWithCertFailed ISFALSE then
      ! only test if connecting to REST service with cert was successful
      call testHasOneOfAttributes    true
      call testInProjectGroup        true
    else
      warn "Skipping remaining attribute validation tests since first triggered an Exception"
    endif

    ! Should get an Exception when try to initialize a second time
    set expectedException          "java.lang.IllegalStateException: Initialization already complete"
    call testInitSSL               false
  else
    warn "Skipping rest of tests since SSL Initialization failed"
  endif
return

!******************************************************************************!
! Test initialization of the SSL Context 
!******************************************************************************!
procedure testInitSSL Z:shouldPass

  ! Set up the SSL context for 2-way SSL based on the user's
  ! private key and the trusted authorities file listed
  ! in the nxm_ssl_configuration_authentication.properties
  ! 
  ! Note: This is effectively the same as:
  !invoke globalM reg.shell.getMidasContext()
  !invoke USERDN nxm.sys.lib.SSLUtilities.initSSLContext(globalM,null,"/home/usr/client_multiple.p12",null)
  sslconfig /INSECURE=^{insecure} /ANYHOSTNAME=^{anyhostname} LABEL=USERDN PROPSFILE=^propsFile PRIVKEYFILE=^pki_private_cert_path

  ! assume distinguished name will at least contain the common name
  if shouldPass then
    invoke sslInitialized nxm.sys.lib.SSLUtilities.isInitDone()
    assert//text="^reg.main.name: SSL Initialization Successful" sslInitialized ISTRUE
    if sslInitialized then
      invoke is_dn userdn.toLowerCase.matches(".*cn=.*")
      assert/text="^reg.main.name: Distinguished name retrieved from certificate" is_dn ISTRUE
    endif
  endif

return

!******************************************************************************!
! Test the validation of multiple attributes for access control
!******************************************************************************!
procedure testHasMultipleAttributes Z:shouldPass

    res t:attrtbl {}
    table t:attrtbl setkey  "firstName" "John"
    table t:attrtbl setkey  "grantedBy" "USA"
    table t:attrtbl setkey  "isIcMember" true

    ! though DN is preferred, uid is allowed
    checkauthorization USERDN="jonesjoh" USERATTR=attrtbl

    if shouldPass then
      assert/text="^reg.main.name: Able to validate multiple user attributes " authorized ISTRUE
    endif

return
!******************************************************************************!
! Test the validation of the existence of one attribute OR another
!******************************************************************************!
procedure testHasOneOfAttributes Z:shouldPass

    res authorized false

    invoke json_attributes nxm.sys.lib.SSLUtilities.getClientAttributes("edisonth")
    invoke is_member nxm.sys.lib.SSLUtilities.verifyUserAttribute(json_attributes,"isIcMember",true)
    invoke is_usa nxm.sys.lib.SSLUtilities.verifyUserAttribute(json_attributes,"country","USA")

    if (is_member OR is_usa) then
      set authorized true
    else
      set authorized false
    endif

    if shouldPass then
      assert/text="^reg.main.name: Able to validate based on having one of user attributes" authorized ISTRUE
    endif

return
!******************************************************************************!
! Test that user is in project group
!******************************************************************************!
procedure testInProjectGroup Z:shouldPass

    checkauth LABEL=in_group USERDN="jonesjoh" PROJECT=project_founding_country GROUP=NAVY

    if shouldPass then
      assert/text="^reg.main.name: Able to validate based on project group" in_group ISTRUE
    endif

return

!******************************************************************************!
! Test jar signing capabilities
! It uses the certificate that is loaded in ssl initialization.
! The test itself takes a few minutes to complete.
! This does not get run if ssl initialization failed.
! since NeXtMidas 3.8.0 to provide testing of jar signing
!******************************************************************************!
procedure testJarSigning
  if ^{env.NMROOT} EQS "classpath:" then
    info "SKIPPING ^reg.main.name since validating a jar file is not supported while running from a jar."
    return
  endif
  info "^reg.main.name: Signing jar file, please do not exit. This may take a few minutes to complete"
  invoke ,, nxm.sys.lib.SSLUtilities.signJarFile(^{env.NMROOT}/jars/nxm-sys.jar)
  invoke ISSIGNED nxm.sys.test.junit.lib.SSLUtilitiesTest.validateJarSigning(^{env.NMROOT}/jars/nxm-sys.jar)
  assert/text="^reg.main.name: Jar file has been signed and verified" ^ISSIGNED ISTRUE
  popup/blocking="MACRO|MOUSE" didFail ok "All tests completed. If there are any exceptions or failures, please fail the test."  
return