#!/bin/bash
set -u
#==============================================================================
# Declarations and Environment
declare ThisScript=${0##*/}
declare ThisUser=
declare ThisHost=${HOSTNAME%%.*}
declare Version=1.1.4
declare Args="$*"
declare CmdLine="$0 $Args"
declare Log=
declare Value=
declare H1="=============================================================================="
declare H2="------------------------------------------------------------------------------"
declare -i ErrorCount=0
declare -i WarningCount=0
declare -i NoOp=1
declare -i Debug=${DEBUG:-0}
declare -i i=0
declare -i ForcePasswordUpdates=0
declare -i DoConfigurableProcessing=1
declare -i DoExtensionProcessing=1
declare -i DoTriggerProcessing=1
declare -i DoUserProcessing=1
declare -i UserIsExempt=0
declare SwarmURL=
declare AccessLevel=
declare CaseHandling=
declare -A SeenGroups
declare SDPInstance=
declare SDPRoot="${SDP_ROOT:-/p4}"
declare SDPCommon="${SDPRoot}/common"
declare SDPCommonBin="${SDPCommon}/bin"
declare SDPCommonCfg="${SDPCommon}/config"
declare SDPCommonLib="${SDPCommon}/lib"
declare SDPCommonSiteBin="${SDPCommon}/site/bin"
declare SSOTriggerDeployed="$SDPCommonSiteBin/triggers/SSO_default.sh"
declare SSOTriggerSource="$SDPRoot/sdp/Unsupported/Samples/triggers/SSO_default.sh"
declare User=
declare UserAuthMethod=
declare UserPasswordSetKeyName=
declare UserPasswordSetKeyValue=
declare ExemptUsersGroup=
declare -i SkipPasswordSetCount=0
declare -i UsersToProcessCount=0
declare -i UsersProcessedOKCount=0
declare -i ExemptUsersToProcessCount=0
declare -a ExemptUsers
declare ExemptUser=
declare -i PrimaryUserExempt=0
declare UsersFile=
declare GroupUsersFile=
declare TriggersFile=
declare TempPasswordFile=
declare TempPassword=
# Color support.
declare GREEN=
declare RED=
declare YELLOW=
declare RESET=
#==============================================================================
# Local Functions
function msg () { echo -e "$*"; }
function msgn () { echo -e -n "$*"; }
function msg_green { msg "${GREEN}$*${RESET}"; }
function msg_yellow { msg "${YELLOW}$*${RESET}"; }
function msg_red { msg "${RED}$*${RESET}"; }
function dbg () { [[ "$Debug" -eq 0 ]] || echo -e "DEBUG: $*" >&2; }
function dbg2 () { [[ "$Debug" -ge 2 ]] && echo -e "DEBUG: $*" >&2; }
function errmsg () { msg_red "\nError: ${1:-Unknown Error}\n"; ErrorCount+=1; }
function warnmsg () { msg_yellow "\nWarning: ${1:-Unknown Warning}\n"; WarningCount+=1; }
function bail () { errmsg "${1:-Unknown Error}"; exit "${2:-1}"; }
#------------------------------------------------------------------------------
# Function get_all_p4_group_users
# This function gets all members of a p4 group, including direct members as well
# as indirect members, i.e. members of subgroups.
function get_all_p4_group_users() {
local group="${1:-}"
[[ -n "$group" ]] || return
[[ -v SeenGroups[$group] ]] && return
SeenGroups[$group]=1
local section=""
while IFS= read -r line; do
case "$line" in
Users:*) section="users" ;;
Subgroups:*) section="subgroups" ;;
[A-Za-z]*:*) section="" ;;
$'\t'*)
local val="${line# }" # strip leading tab
[[ -z "$val" ]] && continue
if [[ "$section" == "users" ]]; then echo "$val"
elif [[ "$section" == "subgroups" ]]; then get_all_p4_group_users "$val"
fi
;;
esac
done < <("$P4BIN" group --exists -o "$1" 2>/dev/null)
}
#------------------------------------------------------------------------------
# Function: run ($cmdAndArgs, $desc, $honorNoOp, $showOutput)
#
# Runs a command, with optional description, showing command line to execute
# and optionally also the output, and capturing and returning the exit code.
# Honors the global NoOp unless honorNoOp is 0.
#
# Input:
# $1 - Command and arguments to execute. Defaults to 'echo'.
# $2 - Optional message to display describing what the command is doing.
# $3 - Honor NoOp mode. If set to one, honor the NoOp variable -- so display
# rather than execute commands.
# $4 - Numeric flag to show output; '1' indicates to show output, 0 to
# suppress it.
#------------------------------------------------------------------------------
function run () {
local cmdAndArgs="${1:-echo}"
local desc="${2:-}"
local honorNoOp="${3:-1}"
local -i showOutput="${4:-1}"
local -i cmdReturnCode=0
local log
[[ -n "$desc" ]] && msg "$desc"
msg "Executing: $cmdAndArgs"
if [[ "$honorNoOp" -eq 1 && "$NoOp" -eq 0 ]]; then
log="$(mktemp "${P4TMP:-/tmp}/run.${ThisScript%.sh}.XXXXXXXXXXX")"
# shellcheck disable=SC2086
eval $cmdAndArgs > "$log" 2>&1
cmdReturnCode=$?
else
msg "NO_OP: Would run: $cmdAndArgs"
cmdReturnCode=0
# In NoOp mode, no command was executed, so there is
# output to show.
showOutput=0
fi
if [[ "$showOutput" -eq 1 ]]; then
echo "EXIT_CODE: $cmdReturnCode" >> "$log"
cat "$log"
fi
if [[ "$honorNoOp" -eq 0 && "$NoOp" -eq 0 ]]; then
/bin/rm -f "$log"
fi
return "$cmdReturnCode"
}
#------------------------------------------------------------------------------
# Function: usage (required function)
#
# Input:
# $1 - style, either -h (for short form) or -man (for man-page like format).
# The default is -h.
#
# $2 - error message (optional). Specify this if usage() is called due to
# user error, in which case the given message displayed first, followed by the
# standard usage message (short or long depending on $1). If displaying an
# error, usually $1 should be -h so that the longer usage message doesn't
# obscure the error message.
#
# Sample Usage:
# usage
# usage -h
# usage -man
# usage -h "Incorrect command line usage."
#------------------------------------------------------------------------------
function usage
{
local style=${1:--h}
local usageErrorMessage=${2:-}
# Set a default value for P4USER so we can run the '-man' option even on a
# machine that does not have SDP properly installed. Any machine with SDP
# installed will have P4USER set reliably.
[[ -n "${P4USER:-}" ]] || P4USER=perforce
[[ -n "$usageErrorMessage" ]] && msg "\n\nUsage Error:\n\n$usageErrorMessage\n\n"
msg "USAGE for $ThisScript v$Version:
$ThisScript -g <GroupOfUsersExemptFromSSO> [-i <SDPInstance>] [-nc] [-ne] [-nt] [-nu] [-f] [-y] [-d|-D] [-L <Log>]
or
$ThisScript [-h|-man|-V]
"
if [[ $style == -man ]]; then
msg "
DESCRIPTION:
This script supports the production cutover to enable Single Sign-On (SSO)
using the Perforce Authentication Service (P4AS). The P4AS service is a
bridge to your Identity Provider (IdP) system, e.g. Google OneLogin, Microsoft
Entra, Okta, Perforce IdP, Ping Federate, etc.
In a typical phased rollout of SSO, SSO is first deployed in a pilot phase
in \"opt-in\" mode, where a few users are manually configured to use SSO.
Then, after testing, the production rollout is done -- that's where this
script comes in. This script supports the production rollout process by
changing a set of users to be ready for SSO rollout. For the production
rollout, this set of users is typically all human users.
Digression: The P4 Server itself is not aware of \"human\" vs. \"non-human\"
users. The user spec does have a 'Type:' field, the value of which can have
values of 'standard', 'service', or 'operator'. The distinction of 'standard'
vs. 'service/operator' users is known to the P4 Server. However, 'service'
and 'operator' users are extremely limited functionally, so much so that
automated accounts are almost always of type 'standard' as far as the P4
Server is concerned.
To call this script, the name of a P4 group containing a list of exempt
users must be provided. This group must contain the user '$P4USER' and
must also contain a list any others users that should not be configured
for SSO. This typically is all non-human accounts such as CI/CD/DevOps
automation, AI agents, etc. It may also contain any users that are not
intended to use SSO for whatever reason (e.g. contractors who are not
defined in your organization's IdP).
The list of users to be processed is starts with the list of users
reported by the 'p4 users' command without the '-a' option (thus naturally
excluding users with a 'Type:' value of 'service' or 'operator', which
cannot use SSO). Then the users in the exempt group are removed. The set
of remaining users are configured for SSO.
This script operates in these phases:
Phase 0: Pre-flight checks.
This phase evaluates readiness of the environment for the SSO cutover, and
performs various checks. If any tests fail, further processing is aborted.
Among the checks are:
- Verifies P4 super user access.
- Checks case-sensitivity of P4 Server.
- Ensures group of exempt users exists.
- Ensures group of exempt users contains SDP P4USER '$P4USER'.
Phase 1: Process Configurables
Check SSO configurables; set if needed:
- Set auth.sso.allow.passwd=1
- Set auth.sso.nonldap=1
- Set auth.default.method=perforce
Phase 1 can be skipped with the '-nc' option.
Phase 2: Process P4AS Extension
Check the P4AS extension, add updated if needed:
- Ensure \"opt-in\" users/groups are NOT defined.
- Ensure \"opt-out\" group references exclusion group specified with '-g'.
- Ensure \"opt-out\" user is the P4USER '$P4USER'.
Phase 2 can be skipped with the '-ne' option.
Phase 3: Process Triggers
Check triggers, add SSO_default.sh trigger if needed.
- Ensure SSO_default.sh trigger script is installed.
- Ensure SSO_default trigger is in the Triggers table.
Phase 3 can be skipped with the '-nt' option.
Phase 4: Processing Users
For all non-exempt uesrs:
- Check AuthMethod, change to 'perforce' if needed.
- Set UUID password.
Phase 4 can be skipped with the '-nu' option.
By default, the password is set only once per user, even if this script is
run multiple times. Use '-f' to always set the password. A warning is
displayed if the password reset is skipped becuase it had been set
previously. Keys named of the form '${ThisScript%.sh}.<NoOp>.<User>'
are set when the password is set successfuly. The '<NoOp>' value is '1'
for a Dry Run and '0' for a Live Run, so that passwords set in Dry Run
mode (which do NOT involve an actually password change) do not affect
passwords set for the Live Run.
Ideally, this script should be run exactly once in Live Run mode. It is
expected that a series of iterative Dry Runs may be needed to refine the
set of users in the group of users excempt from SSO.
Phase 5: P4 Code Review Update
- If P4.Swarm.URL is set, advise considering config.php update.
There is no option to skip Phase 5 becuase it only displays optional
advice; it takes no action.
SAFETY FEATURES:
By default, this script operates in Dry Run (preview) mode, showing
what it would do but not making any changes that affect data. Run
with '-y' to operate for real.
REQUIRED PARAMETERS:
-g <GroupOfUsersExemptFromSSO>
Specify the name of a group containing a list of users that are not to be
configured for to SSO. This group must exist and must contain at least the
the '$P4USER' user.
This parameter is required.
OPTIONS:
-i <SDPInstance>
Specify the SDP instance. If not specified, the \$SDP_INSTANCE variable from
the shell environment is used.
-nc Specify '-nc' to skip SSO Configurables processing.
-ne Specify '-ne' to skip SSO Extension processing.
-nt Specify '-nt' to skip SSO Trigger processing.
-nu Specify '-nu' to skip SSO User processing.
-f Specify that passwords for users that have already been set to a UUID password
are to be reset anyway.
-y Live operation mode. By default, any commands that affect data, such as
setting configurables, are displayed, but not executed. With the '-y' option,
commands affecting data may be executed.
HELP OPTIONS:
-h Display short help message.
-man Display man-style help message.
-V Display script name and version.
LOGGING AND DEBUGGING OPTIONS:
-L <log>
Specify the path to a log file, or the special value 'off' to disable
logging. By default, all output (stdout and stderr) goes to a log file
pointed to by a symlink:
\$LOGS/${ThisScript%.sh}.log
The symlink is for convenience. It refers to the log from the most recent
run if where '-L' was not used.
Each time this script is run, a new timestamped log is started, and
the symlink updated to reference the new/latest log during startup.
NOTE: This script is self-logging. That is, output displayed on the screen
is simultaneously captured in the log file. Using redirection operators like
'> log' or '2>&1' are unnecessary, as is using 'tee' (though using 'tee'
or additional redirects will not interfere with the script).
-d Display debug messages.
-D Set extreme debugging verbosity using bash 'set -x' mode. Implies -d.
EXAMPLES:
Example 1: Dry Run with debug-level verbosity.
$ThisScript -g Non-SSO -d
Example 2: Production Cutover
$ThisScript -g Non-SSO -y
"
fi
exit 2
}
#------------------------------------------------------------------------------
# Function: terminate
# shellcheck disable=SC2317 disable=SC2329
function terminate ()
{
# Disable signal trapping.
trap - EXIT SIGINT SIGTERM
[[ "$Log" == "off" ]] || msg "\nLog is: $Log\n${H1}"
# With the trap removed, exit.
exit "$ErrorCount"
}
#==============================================================================
# SDP Library Functions
if [[ -d "$SDPCommonLib" ]]; then
# shellcheck disable=SC1090 disable=SC1091
source "$SDPCommonLib/logging.lib" ||\
bail "Failed to load bash lib [$SDPCommonLib/logging.lib]. Aborting."
fi
#==============================================================================
# Command Line Processing
declare -i shiftArgs=0
set +u
while [[ $# -gt 0 ]]; do
case $1 in
(-h) usage -h;;
(-man) usage -man;;
(-V|-version|--version) msg "$ThisScript version $Version"; exit 0;;
(-g) ExemptUsersGroup="$2"; shiftArgs=1;;
(-nc) DoConfigurableProcessing=0;;
(-ne) DoExtensionProcessing=0;;
(-nt) DoTriggerProcessing=0;;
(-nu) DoUserProcessing=0;;
(-i) SDPInstance="$2"; shiftArgs=1;;
(-L) Log="$2"; shiftArgs=1;;
(-f) ForcePasswordUpdates=1;;
(-y|--yes) NoOp=0;;
(-d) Debug=1;;
(-d2) Debug=2;;
(-D) Debug=2; set -x;; # Use bash 'set -x' extreme debug mode.
(-*) usage -h "Unknown option ($1).";;
(*) usage -h "Extra parameter [$1] is unknown.";;
esac
# Shift (modify $#) the appropriate number of times.
shift; while [[ $shiftArgs -gt 0 ]]; do
[[ $# -eq 0 ]] && usage -h "Incorrect number of arguments."
shiftArgs=$shiftArgs-1
shift
done
done
set -u
#==============================================================================
# Command Line Verification
[[ -z "$SDPInstance" ]] && SDPInstance="${SDP_INSTANCE:-}"
[[ -z "$SDPInstance" ]] && \
usage -h "The SDP instance parameter is required unless SDP_INSTANCE is set. To set SDP_INSTANCE, do:\n\tsource $SDPCommonBin/p4_vars INSTANCE\n\nreplacing INSTANCE with your SDP instance name."
SDPInstanceVars="$SDPCommonCfg/p4_${SDPInstance}.vars"
[[ -r "$SDPInstanceVars" ]] || \
usage -h "The SDP instance specified [$SDPInstance] is missing Instance Vars file: $SDPInstanceVars"
[[ -z "$ExemptUsersGroup" ]] && \
usage -h "The '-g <GroupOfUsersExemptFromSSO>' parameter is required; it was not specified."
# shellcheck disable=SC1090 disable=SC1091
source "$SDPCommonBin/p4_vars" "$SDPInstance" ||\
bail "Could not do: source \"$SDPCommonBin/p4_vars\" \"$SDPInstance\""
#==============================================================================
# Main Program
trap terminate EXIT SIGINT SIGTERM
# Detect support for colors
if command -v tput >/dev/null 2>&1 && [[ -t 1 ]] && [[ "$(tput colors)" -ge 8 ]]; then
RED="$(tput setaf 1)"
GREEN="$(tput setaf 2)"
YELLOW="$(tput setaf 3)"
RESET="$(tput sgr0)"
else
RED=; GREEN=; YELLOW=; RESET=
fi
# If the user specifies a log file file with '-L', write to the specified file.
# If no log was specified, create a default log file using a timestamp in the
# LOGS dir, and immediately update the symlink for the default log to point to
# it.
if [[ "$Log" != off ]]; then
# If $Log is not yet defined, set it to a reasonable default.
if [[ -z "$Log" ]]; then
LogTimestamp=$(date +'%Y-%m-%d-%H%M%S')
Log="$LOGS/${ThisScript%.sh}.${LogTimestamp}.log"
# Make sure we have a unique log file. Prefer a human-readable timestamp
# using hours/minutes/seconds. Append milliseconds if needed to ensure
# a unique filename.
while [[ -e "$Log" ]]; do
LogTimestamp=$(date +'%Y-%m-%d-%H%M%S.%3N')
Log="$LOGS/${ThisScript%.sh}.${LogTimestamp}.$i.log"
i+=1
done
fi
# The LogLink symlink has no timestamp. It points to the most recent log file.
LogLink="$LOGS/${ThisScript%.sh}.log"
if [[ -e "$LogLink" ]]; then
if [[ -L "$LogLink" ]]; then
rm -f "$LogLink"
else
# If the name that should be a symlink is not a symlink, move it aside before
# creating the symlink.
OldLogTimestamp=$(get_old_log_timestamp "$LogLink")
mv -f "$LogLink" "${LogLink%.log}.${OldLogTimestamp}.log" ||\
bail "Could not move old log file aside; tried: mv -f \"$LogLink\" \"${LogLink%.log}.${OldLogTimestamp}.log\""
fi
fi
touch "$Log" || bail "Couldn't touch new log file [$Log]."
# Use a subshell so the 'cd' doesn't persist.
( cd "$LOGS"; ln -s "${Log##*/}" "${LogLink##*/}"; ) ||\
bail "Couldn't initialize log symlink; tried: ln -s \"$Log\" \"$LogLink\""
# Redirect stdout and stderr to a log file.
if [[ -n "$GREEN" ]]; then
exec > >( tee \
>(sed -r \
-e 's/\x1B\[[0-9;]*[a-zA-Z]//g' \
-e 's/\x1B\(B//g' >>"$Log"))
exec 2>&1
else
exec > >(tee "$Log")
exec 2>&1
fi
msg "${H1}\nLog is: $Log\n"
fi
ThisUser=$(id -n -u)
msg "Starting $ThisScript v$Version as $ThisUser@$ThisHost on $(date) with \n$CmdLine"
if [[ "$NoOp" -eq 0 ]]; then
msg "This is Live Operation Mode."
else
msg "This is a Dry Run/Preview Mode."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 0: Pre-flight checks."
dbg "Getting case handling with: p4 -ztag -F %caseHandling% info -s"
CaseHandling="$("$P4BIN" -ztag -F %caseHandling% info -s)"
if [[ "$CaseHandling" =~ ^sensitive$ ]]; then
dbg "Case-handling mode detected as sensitive."
elif [[ "$CaseHandling" =~ ^insensitive$ ]]; then
dbg "Case-handling mode detected as insensitive."
else
errmsg "Could not determine case handling; mode is [$CaseHandling]."
fi
GroupUsersFile="$(mktemp "${P4TMP:-/tmp}"/group.XXXXXXXX.p4s)"
dbg "Getting members of $ExemptUsersGroup ..."
get_all_p4_group_users "$ExemptUsersGroup" > "$GroupUsersFile"
if [[ -s "$GroupUsersFile" ]]; then
msg_green "Verified: Group '$ExemptUsersGroup' containing users exempt from SSO exists."
else
rm -f "$GroupUsersFile"
errmsg "Could not verify that group '$ExemptUsersGroup' exists."
fi
PrimaryUserExempt=0
while read -r User; do
if [[ "$CaseHandling" == sensitive ]]; then
[[ "$User" == "$P4USER" ]] && PrimaryUserExempt=1
else
# If P4 Server is case-insensitive, do a case-insensitive user comparison.
[[ "${User^^}" == "${P4USER^^}" ]] && PrimaryUserExempt=1
fi
ExemptUsers[ExemptUsersToProcessCount]="$User"
ExemptUsersToProcessCount+=1
done < "$GroupUsersFile"
[[ "$Debug" -eq 0 ]] && rm -f "$GroupUsersFile"
if [[ "$PrimaryUserExempt" -eq 1 ]]; then
msg_green "Verified: Primary user '$P4USER' is among the exempt users."
else
errmsg "Could not verify that the primary user $P4USER is in the exempt users group '$ExemptUsersGroup'. Aborting."
fi
msg "There are $ExemptUsersToProcessCount SSO-exempt users."
ExtensionFileOld=$(mktemp "${P4TMP:-/tmp}"/p4as_extension.XXXXXXXX.p4s)
ExtensionFileNew=$(mktemp "${P4TMP:-/tmp}"/p4as_extension.XXXXXXXX.p4s)
"$P4BIN" extension --configure Auth::loginhook --name loginhook-a1 -o | grep -v ^# > "$ExtensionFileOld" 2>/dev/null
if grep -q '^ExtUUID:' "$ExtensionFileOld" 2>/dev/null; then
msg_green "Verified: P4AS Extension exists."
else
errmsg "The P4AS extension does not exist."
fi
AccessLevel=$("$P4BIN" protects -m)
if [[ "$AccessLevel" == super ]]; then
msg_green "Verified: Access Level is super, as required."
else
errmsg "Access Level is '$AccessLevel'; super is required."
fi
if [[ "$ErrorCount" -eq 0 ]]; then
msg_green "\nAll Pre-flight checks passed. Proceeding."
else
errmsg "Aborting due to failed pre-flight checks."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 1: Setting SSO-related configurables.\n"
if [[ "$DoConfigurableProcessing" -eq 1 ]]; then
for c in auth.sso.allow.passwd auth.sso.nonldap; do
Value=$("$P4BIN" -ztag -F %Value% configure show "$c")
if [[ "$Value" == 0 ]]; then
run "p4 configure set $c=1" \
"\nSetting configurable $c."
else
msg_green "Verified: $c is set to $Value."
fi
done
Value=$("$P4BIN" -ztag -F %Value% configure show auth.default.method)
if [[ "$Value" != perforce ]]; then
run "p4 configure set auth.default.method=perforce" \
"\nSetting configurable auth.default.method=perforce."
else
msg_green "Verified: auth.default.method is set to perforce."
fi
else
msg "Skipping configurable processing due to '-nc'."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 2: Changing P4AS extension to opt-out mode.\n"
if [[ "$DoExtensionProcessing" -eq 1 ]]; then
export ExemptUsersGroup P4USER
perl -0777 -pe '
# 1. Replace sso-groups: and all following indented lines
s/(sso-groups:\n)(\t\t[^\n]*\n)+/${1}\t\t... (none)\n/g;
# 2. Replace sso-users: and all following indented lines
s/(sso-users:\n)(\t\t[^\n]*\n)+/${1}\t\t... (none)\n/g;
# 3. Target non-sso-groups: and ONLY the single line immediately following it
s/(non-sso-groups:\n)\t\t[^\n]*\n/${1}\t\t$ENV{ExemptUsersGroup}\n/g;
# 4. Target non-sso-users: and ONLY the single line immediately following it
s/(non-sso-users:\n)\t\t[^\n]*\n/${1}\t\t$ENV{P4USER}\n/g;
' "$ExtensionFileOld" > "$ExtensionFileNew"
if grep -q "$P4USER" "$ExtensionFileNew" 2>/dev/null; then
msg_green "Sanity check on new Extension config is AOK."
msg "Extension changes to be made:"
diff "$ExtensionFileOld" "$ExtensionFileNew"
msg "Updating Extension to change from opt-in to opt-out mode."
if [[ "$NoOp" -eq 0 ]]; then
dbg "Running: $P4BIN extension --configure Auth::loginhook --name loginhook-a1 -i"
dbg "Contents of updated extension:\n$(cat "$ExtensionFileNew")"
if "$P4BIN" extension --configure Auth::loginhook --name loginhook-a1 -i < "$ExtensionFileNew"; then
rm -f "$ExtensionFileOld" "$ExtensionFileNew"
else
errmsg "Error updating extension with this new config: $ExtensionFileNew"
fi
else
msg "NO_OP: Would run: $P4BIN extension --configure Auth::loginhook --name loginhook-a1 -i"
dbg "Contents of updated extension:\n$(cat "$ExtensionFileNew")"
fi
else
errmsg "This updated Extension config failed a sanity check:"
cat "$ExtensionFileNew"
fi
else
msg "Skipping extension processing due to '-ne'."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 3: Processing Triggers."
if [[ "$DoTriggerProcessing" -eq 1 ]]; then
TriggersFile=$(mktemp "${P4TMP:-/tmp}"/triggers.XXXXXXXX.p4s)
dbg "Extracting triggers with: p4 triggers -o | grep -v ^# | grep -v ^Update: > $TriggersFile"
"$P4BIN" triggers -o | grep -v ^# | grep -v ^Update: | sed '$d' > "$TriggersFile"
if grep -q SSO_default "$TriggersFile"; then
msg_green "Verified; SSO_default trigger is defined in Triggers table."
else
echo -e "\tSSO_default form-save user \"$SSOTriggerDeployed %formfile% none\"\n\tSSO_default form-commit user \"$SSOTriggerDeployed %formfile% none\"\n" >> "$TriggersFile"
run "p4 -s triggers -i < $TriggersFile" \
"Updating Triggers table to add SSO_default triggers." ||\
bail "Failed to append SSO_default trigger entries to Trigger table using file: $TriggersFile. Aborting"
fi
if [[ -x "$SSOTriggerDeployed" ]]; then
msg_green "Verified: Trigger script is executable and installed as: $SSOTriggerDeployed"
else
if [[ -r "$SSOTriggerSource" ]]; then
msg "Installing SSO_default trigger."
run "mkdir -p ${SSOTriggerDeployed%/*}" "Ensuring triggers dir '${SSOTriggerDeployed%/*}' exists." ||\
warnmsg "Attempt to create triggers dir failed; this may lead to more failures."
run "cp -f $SSOTriggerSource $SSOTriggerDeployed" "Installing SSO_default trigger." ||\
errmsg "Failed to copy SSO_default trigger."
run "chmod +x $SSOTriggerDeployed" "chmod +x $SSOTriggerDeployed" ||\
errmsg "Failed to do: chmod +x $SSOTriggerDeployed"
else
warnmsg "Could not find trigger source: $SSOTriggerSource; skipping install."
fi
fi
[[ "$Debug" -eq 0 ]] && rm -f "$TriggersFile"
else
msg "Skipping trigger processing due to '-nt'."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 4: Processing Users."
if [[ "$DoUserProcessing" -eq 1 ]]; then
UsersFile="$(mktemp "${P4TMP:-/tmp}"/users.XXXXXXXX.txt)"
dbg "Determining list of all users of type 'standard' with: p4 -ztag -F %User% users > $UsersFile"
# If there is any stderr, allow it to flow thru to the log, don't redirect
# to the temp file. We can safely assume there will be no stderr if the
# exit code is zero.
"$P4BIN" -ztag -F %User% users > "$UsersFile" ||\
bail "Could not get list of users with: $P4BIN -ztag -F %User% users. Aborting."
while read -r User; do
dbg2 "Considering user [$User]."
UserIsExempt=0
for ExemptUser in "${ExemptUsers[@]}"; do
if [[ "$ExemptUser" == "$User" ]]; then
UserIsExempt=1
break
fi
done
if [[ "$UserIsExempt" -eq 1 ]]; then
dbg "Skipping exempt user '$User'."
continue
fi
msg "\nProcessing user '$User'."
UsersToProcessCount+=1
UserAuthMethod=$("$P4BIN" -ztag -F %AuthMethod% user -o "$User")
if [[ "$UserAuthMethod" == perforce ]]; then
msg_green "Verified: AuthMethod for user '$User' is already 'perforce'."
else
msg "Changing AuthMethod for user '$User' from '$UserAuthMethod' to 'perforce'."
UserFile="$(mktemp "${P4TMP:-/tmp}"/user.XXXXXXXX.p4s)"
"$P4BIN" --field AuthMethod=perforce user -o "$User" > "$UserFile"
if run "$P4BIN user -f -i < $UserFile" \
"Setting AuthMethod for user '$User' to 'perforce'."; then
dbg "AuthMethod set OK for user '$User'."
else
errmsg "Failed to set AuthMethod for user '$User' to 'perforce'."
continue
fi
fi
# Use 'p4 keys' data to track whether the password has already been set.
UserPasswordSetKeyName="${ThisScript%.sh}.$NoOp.$User"
UserPasswordSetKeyValue=$("$P4BIN" key "$UserPasswordSetKeyName")
if [[ "$UserPasswordSetKeyValue" == 1 ]]; then
if [[ "$ForcePasswordUpdates" -eq 0 ]]; then
warnmsg "UUID password for user '$User' was set previously; not setting it again. Use '-f' to force reset."
UsersProcessedOKCount+=1
SkipPasswordSetCount+=1
continue
else
warnmsg "UUID password for user '$User' was set previously; setting it again due to '-f'."
fi
fi
UserPasswordSetKeyValue=$((UserPasswordSetKeyValue+1))
TempPassword=$(uuidgen 2>/dev/null)
[[ -n "$TempPassword" ]] || TempPassword="$RANDOM$RANDOM$RANDOM"
TempPasswordFile=$(mktemp)
touch "$TempPasswordFile"
chmod 600 "$TempPasswordFile"
echo -e "$TempPassword" > "$TempPasswordFile"
echo -e "$TempPassword" >> "$TempPasswordFile"
if run "$P4BIN passwd $User < $TempPasswordFile" "Setting password for user '$User'"; then
# We don't use 'run()' for this 'p4 key' command; just set the key.
# The key name includes the '$NoOp' setting, so key values from
# Dry Runs do not interact with those from Live Runs.
# Undoc feature: The key counts the number of times the password was
# reset for each user (if '-f' was used). Not sure if this is useful
# but it's easy to track.
"$P4BIN" key "$UserPasswordSetKeyName" "$UserPasswordSetKeyValue"
UsersProcessedOKCount+=1
else
errmsg "Failed to set password for user '$User'."
fi
rm -f "$TempPasswordFile"
TempPassword=
done < "$UsersFile"
[[ "$Debug" -eq 0 ]] && rm -f "$UsersFile"
else
msg "Skipping user processing due to '-nu'."
fi
#------------------------------------------------------------------------------
msg "${H2}\nPhase 5: P4 Code Revew Updates."
SwarmURL=$("$P4BIN" property -n P4.Swarm.URL -l)
if [[ -n "$SwarmURL" ]]; then
msg "P4 Code Review appears to be operating in this environment with URL: $SwarmURL\n"
msgn "Becuase P4 Code Review is used, "
else
msg "P4 Code Review does not appear to be operating in this environment; the P4.Swarm.URL property is not yet.\n"
msgn "If P4 Code Review is used, "
fi
msg "update the config.php on the Swarm\nserver by adding the 'sso' block to the 'p4' array. See:\nhttps://help.perforce.com/helix-core/helix-swarm/swarm/current/Content/Swarm/admin-saml_php_config.html"
#------------------------------------------------------------------------------
msg "${H2}\nSummary:
Users To Process: $UsersToProcessCount
Processed OK: $UsersProcessedOKCount
Passwords Skipped $SkipPasswordSetCount
Errors: $ErrorCount
Warnings: $WarningCount
"
if [[ "$ErrorCount" -eq 0 && "$WarningCount" -eq 0 ]]; then
msg_green "Success: Processing completed with no errors or warnings."
elif [[ "$ErrorCount" -eq 0 ]]; then
warnmsg "Processing completed with no errors, but $WarningCount warnings were encountered."
else
errmsg "Processing completed, but $ErrorCount errors and $WarningCount warnings were encountered."
fi
#------------------------------------------------------------------------------
# See the terminate() function where this script really exits.
exit "$ErrorCount"
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #11 | 32462 | C. Thomas Tyler | Fixed so docs can be generated on Mac. | ||
| #10 | 32461 | C. Thomas Tyler |
Added Phase 5 for Swarm changes. This phase takes no new action; it's just a reminder to update P4 Code Review if needed. |
||
| #9 | 32456 | C. Thomas Tyler | Bug fixes during QA. | ||
| #8 | 32455 | C. Thomas Tyler | Refined Perl regex. | ||
| #7 | 32454 | C. Thomas Tyler |
Added code to change Extension from "opt-in" for PoC to "opt-out" for full production rollout of P4AS/SSO. |
||
| #6 | 32453 | C. Thomas Tyler | Fixed path to SSO_default.sh trigger file. | ||
| #5 | 32451 | C. Thomas Tyler |
Fixed so case-handling behavior of P4 Server affects whether the comparison of the exempt user. Added docs about pre-flight checks. |
||
| #4 | 32450 | C. Thomas Tyler |
Added handling of auth.default.method. Added logic to avoid setting passwords more than once in Dry Run and separately in Live modes, with '-f' option to override. |
||
| #3 | 32449 | C. Thomas Tyler | Added logic to skip setting of passwords that were already skipped. | ||
| #2 | 32448 | C. Thomas Tyler | Semi-working version. | ||
| #1 | 32447 | C. Thomas Tyler | WIP version added. |