DefaultLoginPanel.java

/*
** Module   : DefaultLoginPanel.java
** Abstract : implements a login panel to get the userid/pw when there is no
**            custom panel provided
**
** Copyright (c) 2013-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- -------------------------------- Description -------------------------------
** 001 GES 20111019 First version.
** 002 OM  20131018 Updated to conform the new Authenticator API.
** 003 GES 20140626 Renamed color attributes to match naming standards.
** 004 CA  20140805 ThinClient is explicitly imported, to isolate ChUI class usage outside of ChUI
**                  packages.
** 005 EVL 20140829 All widgets in login screen must have predefined ID.  Including space and skip
**                  never leaving -1 as ID when passing widget config to ScreenDefinition class.
**                  We can not use the new widget ID allocation here because there is no
**                  LogicalTerminal at this time.
** 006 CA  20140926 Added shared widget configuration support, to allow GUI/ChUI concrete
**                  implementation for the same widget ID.  Refactored the widget configuration
**                  classes: all fields were made public; these classes need to be as dumb as
**                  possible, all logic related to setting/getting a certain field should be at
**                  the widget or at the caller. Refs #2254
** 007 VIG 20140911 Fixed status method calls with corresponding arguments for changed signature.
** 008 CA  20141119 Refactored drawing code to enable multiple OS windows, for GUI support.  Misc
**                  changes/fixes related to drawing on multiple windows.
** 009 HC  20150405 Refactoring of config coordinate fields.
** 010 HC  20151013 Changes to extend GUI multi-window focus management and ACTIVE-WINDOW system
**                  handle processing to modal windows.
** 011 EVL 20160726 Now we use screen buffer array in waitFor() result instead of single buffer.
** 012 SBI 20161101 Added tab item list as a parameter to the enable, view, display,
**                  displayAndDown and promptFor methods provided by ClientExports.
** 013 CA  20180620 Ensure both on/off calls for an invalidation bracket are always called.
** 014 GBB 20230825 checkCallerAbort moved to SecurityUtil & calls updated.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.security;

import com.goldencode.p2j.net.SessionListener;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.ui.chui.ThinClient;
import com.goldencode.p2j.ui.client.*;
import com.goldencode.p2j.util.*;
import java.util.*;

/**
 * Hard coded user interface which displays a prompt to obtain the login
 * credentials (userid and password). This is coded to work as a client-side
 * authentication plugin called from the security manager. While in operation,
 * the client environment and session are not fully initialized. This means
 * that processing must be limited.
 */
public class DefaultLoginPanel
implements Authenticator
{
   /** Client interface. */
   private ThinClient client = null; 

   /** Edit frame definition. */
   private ScreenDefinition editsd = null; 
   
   /** Edit frame ID. */
   private static final int EDIT_FID = 100; 
   
   /** User ID field ID. */
   private static final int USER_ID = EDIT_FID + 1;

   /** Password field ID. */
   private static final int PASSWORD_ID = USER_ID + 2; // 1 + skip

   /** Standard login status message. */
   private static final String STD_MSG = "Enter login credentials.";

   /** Invalid password status message. */
   private static final String INVALID_PASSWORD = "Invalid password!";
   
   /** Invalid user status message. */
   private static final String INVALID_USER = "Unknown user name!";

   /** The screen buffer for the editing frame. */
   private ScreenBuffer editsb = new ScreenBuffer(EDIT_FID, 3);
   
   /** The userid typed last time this was used. */
   private String lastid = null;
      
   /** The password typed last time this was used. */
   private String lastpw = null;

   /**
    * Default constructor. 
    */
   public DefaultLoginPanel()
   {
      client = ThinClient.getInstance();
   }

   /**
    * Implements client side custom authentication logic.
    *
    * @param    parameters
    *           Additional configuration parameters. This plugin uses
    *           "option" parameter which is taken from the directory.
    * @param    code
    *           The result of the most recent attempt to authenticate or
    *           <code>AUTH_RESULT_NONE</code> if this is the first attempt.
    *
    * @return   Array of bytes to be transmitted to the server as the
    *           authorization input. 
    */
   @Override
   public byte[] clientAuthHook(Map<String, Object> parameters, int code)
   {
      OutputManager<?> tk = OutputManager.instance();
      
      // there are two tab items
      int[] tabItemList = new int[] {USER_ID, PASSWORD_ID};
      
      // push the frame definition the first time in
      if (code == AUTH_RESULT_NONE)
      {
         // create the frame definition on the fly
         editsd = new ScreenDefinition(EDIT_FID);
   
         // edit frame configuration
         FrameConfig editfc = new FrameConfig();
         editfc.row = 10;
         editfc.box = true;
         editfc.centered = true;
         editfc.overlay = true;
         editfc.dcolor = Color.NORMAL;
         editfc.sideLabels = true;
         editsd.addConfig(editfc, EDIT_FID);
         
         FillInConfig fic;
         
         // user ID entry field
         fic = new FillInConfig();
         fic.dataType = "character";
         fic.format = "x(25)";
         fic.label = " User ID";
         editsd.addConfig(fic, USER_ID);
         
         // set next widget position
         addSkip(editsd, 0, USER_ID + 1);
   
         // password entry field
         fic = new FillInConfig();
         fic.dataType = "character";
         fic.format = "x(25)";
         fic.label = "Password";
         fic.blank = true;
         editsd.addConfig(fic, PASSWORD_ID);

         // instantiate screen data
         client.pushScreenDefinition(new ScreenDefinition[] {editsd});

         // set status line for input operation
         client.statusInput(STD_MSG, WindowConfig.RESOLVE_WINDOW);
      }
      else
      {
         String msgtxt = INVALID_PASSWORD;
         
         if (code == AUTH_RESULT_INVALID_USERID)
         {
            msgtxt = INVALID_USER;
         }
         
         // display error
         client.message(msgtxt);
      }

      // process the edit frame screen buffer
      if (lastid != null)
      {
         // on errors, we have to display the previously typed userid
         editsb.putWidgetValue(USER_ID, new character(lastid));
      }

      String id = null;
      String pw = null;

      EventList el = new EventList(true);
      ScreenBuffer[] sbf = null;

      Window<?> window = WindowManager.getDefaultWindow();
      
      while (true)
      {
         // drive the logon frame I/O
         tk.setInvalidate(true);
         try
         {
            client.enable(EDIT_FID, null, new ScreenBuffer[] {editsb}, true,
                          WindowConfig.RESOLVE_WINDOW, tabItemList);
            client.clear(EDIT_FID, true);
            client.view(EDIT_FID, new ScreenBuffer[] {editsb}, null, true,
                        WindowConfig.RESOLVE_WINDOW, tabItemList);
            editsb.resetChanged();
         }
         finally
         {
            tk.setInvalidate(window, false);
         }

         try
         {
            client.setAuthMode(true);
            sbf = client.waitFor(el, USER_ID, -1, new ScreenBuffer[] {editsb});
         }
         
         catch (ConditionException cx)
         {
            continue;
         }
         
         finally
         {
            client.setAuthMode(false);
         }

         tk.setInvalidate(true);
         try
         {
            client.enable(EDIT_FID, null, new ScreenBuffer[] {editsb}, false,
                          WindowConfig.RESOLVE_WINDOW, tabItemList);
         }
         finally
         {
            tk.setInvalidate(window, false);
         }
   
         // inspect the input
         int keyCode = sbf[0].getKeyCode();

         // exit out if requested
         if (Keyboard.keyFunction(keyCode).equals("END-ERROR"))
            return new byte[] {};

         character idc = (character) sbf[0].getWidgetValue(USER_ID);
         
         if (idc == null && lastid == null)
            continue;

         id = (idc != null) ? idc.getValue().trim() : lastid;
         lastid = id;

         character pwc = (character) sbf[0].getWidgetValue(PASSWORD_ID);
         
         if (pwc == null && lastpw == null)
         {
            pw = "";
         }
         else
         {
            pw = (pwc != null) ? pwc.getValue() : lastpw;
            lastpw = pw;
         }
         
         break; 
      }

      // return a byte array
      return SecurityUtil.packageIdPassword(id, pw);      
   }
   
   /**
    * Finalizes any resources allocated during authentication by the client.
    */
   @Override
   public void clientFinalize()
   {
      client.statusInputRevert(WindowConfig.RESOLVE_WINDOW);
      client.hideAll(true, WindowConfig.RESOLVE_WINDOW);
      client.destroyFrame(EDIT_FID);
   }

   /**
    * Not used for the client side.
    *
    * @param    auth
    *           The authorization input from the client.
    * @param    entity
    *           The entity to be authenticated.
    *
    * @return   Always <code>null</code>.
    */
   @Override
   public AuthenticationResponse serverAuthHook(byte[] auth, String entity)
   {
      return null;
   }

   /**
    * Not used for the client side, always returns <code>null</code>.
    *
    * @return  Always <code>null</code>.
    */
   @Override
   public SessionListener getSessionListener()
   {
      return null;
   }

   /**
    * Returns a set of entities that this class handles. Not used for the client side.
    *
    * @return  Always <code>null</code>.
    */
   @Override
   public Set<String> getAuthenticationEntities()
   {
      return null;
   }

   /**
    * Configures the Authenticator by setting the "option" parameter from directory.xml.
    * Not used for the client side.
    *
    * @param   option
    *          The value of "option" entry for the auth plugin.
    */
   @Override
   public void configure(String option)
   {
      // nop
   }

   /**
    * Add specified number of empty lines
    *
    * @param   sd
    *          The frame to which to add a skip.
    * @param   lines
    *          Lines to skip.
    * @param   id
    *          Widget ID to be used for this skip widget.
    */
   private void addSkip(ScreenDefinition sd, int lines, int id)
   {
      SkipConfig sc = new SkipConfig();
      sc.vertical = true;   // skip not space
      sc.length = lines;
      sd.addConfig(sc, id);
   }
}