diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index 0b4b2728e..391c75eaf 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -1,4 +1,5 @@ skip_non_tags: true +image: Visual Studio 2019 clone_folder: c:\gopath\src\github.com\versent\saml2aws environment: GOPATH: c:\gopath @@ -6,7 +7,8 @@ environment: secure: 3kWTz99Qj+ipyaR73CxcJeGRRbmk84MF2ERDu6MyY10cjHAi6s3AVZ2Ccoa+Ioyt appName: saml2aws install: -- set PATH=C:\msys64\mingw64\bin;%PATH% +- set PATH=C:\msys64\mingw64\bin;C:\go120\bin;%PATH% +- set GOROOT=C:\go120 - ps: >- $VerbosePreference = 'Continue' diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..6162e9cc4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.buildtemp \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1bb13ea22..1e0337155 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,3 +6,9 @@ updates: interval: "weekly" labels: - "type: dependencies" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + labels: + - "type: dependencies" diff --git a/.github/win-msi/out/.gitignore b/.github/win-msi/out/.gitignore new file mode 100644 index 000000000..1287e9bd7 --- /dev/null +++ b/.github/win-msi/out/.gitignore @@ -0,0 +1,2 @@ +** +!.gitignore diff --git a/.github/win-msi/src/saml2aws.wxs b/.github/win-msi/src/saml2aws.wxs new file mode 100644 index 000000000..f1b2c8571 --- /dev/null +++ b/.github/win-msi/src/saml2aws.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/win-msi/wix.sh b/.github/win-msi/wix.sh new file mode 100644 index 000000000..cbf8dc092 --- /dev/null +++ b/.github/win-msi/wix.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +candle src/saml2aws.wxs -dSaml2AwsVer=${VERSION} -o "out/" +light -sval "out/saml2aws.wixobj" -o "out/saml2aws_${VERSION}_windows_amd64.msi" diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index bbb163c98..ae17d3730 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -2,12 +2,11 @@ name: Go on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: - build: name: Build runs-on: ${{ matrix.os }} @@ -15,54 +14,97 @@ jobs: matrix: os: [ubuntu-latest, macOS-latest, macos-11] steps: + - name: Set up Go 1.x + uses: actions/setup-go@v4 + with: + go-version: 1.20.x - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: 1.17.x + - name: Check out code into the Go module directory + uses: actions/checkout@v4 - - name: Check out code into the Go module directory - uses: actions/checkout@v2 + - name: Test + run: | + go test -v ./... -coverprofile=${{ matrix.os }}_coverage.txt -covermode=atomic - - name: Test - run: go test -v ./... + - name: Upload coverage report + uses: actions/upload-artifact@v3 + with: + name: reports + path: ${{ matrix.os }}_coverage.txt + if-no-files-found: error + retention-days: 1 - - name: Install - run: go install ./cmd/saml2aws + - name: Install + run: go install ./cmd/saml2aws linting: name: lint runs-on: ubuntu-latest steps: + - name: Set up Go 1.x + uses: actions/setup-go@v4 + with: + go-version: 1.20.x - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: 1.17.x + - name: Check out code into the Go module directory + uses: actions/checkout@v4 - - name: Check out code into the Go module directory - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.53.2 + args: --timeout=2m - - name: golangci-lint - uses: golangci/golangci-lint-action@v2 - with: - version: v1.42.0 + coverage: + name: coverage + permissions: + contents: read + runs-on: ubuntu-latest + needs: [build] + steps: + - uses: actions/checkout@v4 + - name: Download coverage reports + uses: actions/download-artifact@v3 + with: + name: reports + path: reports + + - name: Codecov + uses: codecov/codecov-action@v3 + with: + directory: reports + flags: unittests release-build: name: release-build - runs-on: ubuntu-latest + strategy: + matrix: + os: + - ubuntu-latest + - ubuntu-20.04 + - macos-latest + runs-on: ${{ matrix.os }} steps: + - name: Set up Go 1.x + uses: actions/setup-go@v4 + with: + go-version: 1.20.x + + - name: Check out code into the Go module directory + uses: actions/checkout@v4 - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: 1.17.x + - name: Install dependency required for linux builds + if: matrix.os == 'ubuntu-20.04' + run: sudo apt-get update && sudo apt-get install -y libudev-dev - - name: Check out code into the Go module directory - uses: actions/checkout@v2 + - name: GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + version: latest + args: build --snapshot --clean --config .goreleaser.${{ matrix.os }}.yml - - name: GoReleaser - uses: goreleaser/goreleaser-action@v2 - with: - version: latest - args: build --snapshot --rm-dist + - name: Upload + uses: actions/upload-artifact@v3 + with: + name: saml2aws + path: dist/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b6d9847ef..5577d09bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,25 +4,113 @@ on: push: tags: - '*' + workflow_dispatch: + inputs: + tag: + description: The tag to run against. This trigger only runs the MSI builder. + required: true jobs: release: name: release - runs-on: macOS-latest + strategy: + # the goreleaser and the Github release API doesn't handle concurrent + # access well, so run goreleaser serially + max-parallel: 1 + matrix: + os: + - ubuntu-latest + - ubuntu-20.04 + - macos-latest + runs-on: ${{ matrix.os }} + if: github.event_name != 'workflow_dispatch' + permissions: write-all steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.17.x + go-version: 1.20.x - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v4 + - name: Install dependency required for linux builds + if: matrix.os == 'ubuntu-20.04' + run: sudo apt-get update && sudo apt-get install -y libudev-dev + + - name: Add Lowercase Repository Name to Environment + run: | + echo REPOSITORY_NAME_LOWERCASE=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + + - uses: "docker/login-action@v3" + if: matrix.os == 'ubuntu-20.04' + with: + registry: "ghcr.io" + username: "${{ github.actor }}" + password: "${{ secrets.GITHUB_TOKEN }}" - name: GoReleaser - uses: goreleaser/goreleaser-action@v2 + uses: goreleaser/goreleaser-action@v5 with: version: latest - args: release --rm-dist + args: release --clean --config .goreleaser.${{ matrix.os }}.yml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + IMAGE_NAME: ${{ env.REPOSITORY_NAME_LOWERCASE }} + + windows-msi: + name: Build Windows MSI and upload to release + runs-on: ubuntu-latest + permissions: + contents: write + needs: [release] + if: >- # https://github.com/actions/runner/issues/491 + always() && + (needs.release.result == 'success' || needs.release.result == 'skipped') + env: + INSTALLER: ${{ github.workspace }}/.github/win-msi + BIN: ${{ github.workspace }}/.github/win-msi/src/bin + WIXIMG: dactiv/wix@sha256:17d232708589641f5632f9a1ff9463ad087b192cea7b8e6012d2b47ec6af5f6c + steps: + - name: Normalize tag values + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] ; then + VER=${{ github.event.inputs.tag }} + else + VER=${GITHUB_REF/refs\/tags\//} + fi + + VERSION=${VER//v} + + echo "VER_TAG=$VER" >> $GITHUB_ENV + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "ASSET=saml2aws_${VERSION}_windows_amd64.zip" >> $GITHUB_ENV + + - name: Check out code + uses: actions/checkout@v4 + + - name: Retrieve the release asset + id: asset + uses: robinraju/release-downloader@efa4cd07bd0195e6cc65e9e30c251b49ce4d3e51 # v1.8 + with: + repository: ${{ github.repository }} + tag: ${{ env.VER_TAG }} + fileName: ${{ env.ASSET }} + out-file-path: ${{ env.BIN }} + + - name: Unzip asset + working-directory: ${{ env.BIN }} + run: unzip "${ASSET}" + + - name: Build MSI + run: | + # container does not run as root + chmod -R o+rw "${INSTALLER}" + + cat "${INSTALLER}/wix.sh" | docker run --rm -i -e VERSION -v "${INSTALLER}:/wix" ${WIXIMG} /bin/sh + + - name: Upload the asset to the release + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 / v1 + with: + tag_name: ${{ env.VER_TAG }} + files: ${{ env.INSTALLER }}/out/*.msi diff --git a/.gitignore b/.gitignore index 186524f9f..dab5a8422 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ vendor /package /stage coverage.txt +coverage.xml .ctags .vscode bin/ @@ -19,3 +20,4 @@ bin/ # direnv .envrc +.buildtemp \ No newline at end of file diff --git a/.golangci.yaml b/.golangci.yaml index 8cd3c6a64..2d5a1d26d 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -2,13 +2,10 @@ linters: disable-all: true enable: - goimports - - deadcode - errcheck - gosimple - govet - ineffassign - staticcheck - - structcheck - typecheck - unused - - varcheck diff --git a/.goreleaser.yml b/.goreleaser.macos-latest.yml similarity index 82% rename from .goreleaser.yml rename to .goreleaser.macos-latest.yml index 393f7c395..de65cbc4a 100644 --- a/.goreleaser.yml +++ b/.goreleaser.macos-latest.yml @@ -10,9 +10,7 @@ builds: ldflags: - -s -w -X main.Version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} goos: - - windows - darwin - - linux goarch: - amd64 - arm64 @@ -20,10 +18,9 @@ builds: archives: - format: tar.gz wrap_in_directory: false - format_overrides: - - goos: windows - format: zip # remove README and LICENSE files: - LICENSE.md - README.md +checksum: + name_template: "{{ .ProjectName }}_{{ .Version }}_darwin_checksums.txt" diff --git a/.goreleaser.ubuntu-20.04.yml b/.goreleaser.ubuntu-20.04.yml new file mode 100644 index 000000000..497fe4787 --- /dev/null +++ b/.goreleaser.ubuntu-20.04.yml @@ -0,0 +1,94 @@ +--- +project_name: saml2aws-u2f + +builds: +- id: saml2aws + main: ./cmd/saml2aws/main.go + binary: saml2aws + flags: + - -trimpath + - -v + ldflags: + - -s -w -X main.Version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} + goos: + - linux + goarch: + - amd64 + overrides: + - goos: linux + goarch: amd64 + goamd64: v1 + tags: + - hidraw + env: + - CGO_ENABLED=1 +- id: saml2aws-static + main: ./cmd/saml2aws/main.go + binary: saml2aws + flags: + - -trimpath + - -v + ldflags: + - -s -w -X main.Version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -extldflags "-static" + goos: + - linux + goarch: + - amd64 + - arm64 + - arm + env: + - CGO_ENABLED=0 +archives: + - id: saml2aws + format: tar.gz + builds: [saml2aws] + wrap_in_directory: false + # remove README and LICENSE + files: + - LICENSE.md + - README.md + - id: saml2aws-static + format: tar.gz + builds: [saml2aws-static] + wrap_in_directory: false + # remove README and LICENSE + files: + - LICENSE.md + - README.md + name_template: "{{ .ProjectName }}_static_{{ .Version }}_{{ .Os }}_{{ .Arch }}" +checksum: + name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt" +dockers: + - id: amd64 + goos: linux + goarch: amd64 + use: buildx + ids: + - saml2aws-static + image_templates: + - ghcr.io/{{ .Env.IMAGE_NAME }}:{{ .Version }}-amd64 + - ghcr.io/{{ .Env.IMAGE_NAME }}:latest-amd64 + build_flag_templates: + - "--build-arg=BASE_IMAGE_ARCH=static-debian11" + - "--platform=linux/amd64" + - id: arm64 + goos: linux + goarch: arm64 + use: buildx + ids: + - saml2aws-static + image_templates: + - ghcr.io/{{ .Env.IMAGE_NAME }}:{{ .Version }}-arm64 + - ghcr.io/{{ .Env.IMAGE_NAME }}:latest-arm64 + build_flag_templates: + - "--build-arg=BASE_IMAGE_ARCH=static:latest-arm64" + - "--platform=linux/arm64" +docker_manifests: + - name_template: ghcr.io/{{ .Env.IMAGE_NAME }}:{{ .Version }} + image_templates: + - ghcr.io/{{ .Env.IMAGE_NAME }}:{{ .Version }}-amd64 + - ghcr.io/{{ .Env.IMAGE_NAME }}:{{ .Version }}-arm64 + - name_template: ghcr.io/{{ .Env.IMAGE_NAME }}:latest + image_templates: + - ghcr.io/{{ .Env.IMAGE_NAME }}:latest-amd64 + - ghcr.io/{{ .Env.IMAGE_NAME }}:latest-arm64 \ No newline at end of file diff --git a/.goreleaser.ubuntu-latest.yml b/.goreleaser.ubuntu-latest.yml new file mode 100644 index 000000000..5db45000c --- /dev/null +++ b/.goreleaser.ubuntu-latest.yml @@ -0,0 +1,36 @@ +--- +project_name: saml2aws + +builds: +- main: ./cmd/saml2aws/main.go + binary: saml2aws + flags: + - -trimpath + - -v + ldflags: + - -s -w -X main.Version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} + goos: + - windows + - linux + goarch: + - amd64 + - arm64 + - arm + overrides: + - goos: linux + goarch: amd64 + goamd64: v1 + env: + - CGO_ENABLED=0 +archives: + - format: tar.gz + wrap_in_directory: false + format_overrides: + - goos: windows + format: zip + # remove README and LICENSE + files: + - LICENSE.md + - README.md +checksum: + name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..5a030a50e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +ARG BASE_IMAGE_ARCH=static-debian11 +FROM gcr.io/distroless/$BASE_IMAGE_ARCH +COPY saml2aws / +ENTRYPOINT ["/saml2aws"] \ No newline at end of file diff --git a/Dockerfile.build b/Dockerfile.build new file mode 100644 index 000000000..8ba3ea493 --- /dev/null +++ b/Dockerfile.build @@ -0,0 +1,37 @@ +# base image +FROM ubuntu:jammy +ENV TZ=Australia/Sydney +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# add arm64 architecture +RUN apt-get update +RUN dpkg --add-architecture arm64 + +## arch-qualify the current repositories +RUN sed -i "s/deb h/deb [arch=amd64] h/g" /etc/apt/sources.list + +## add arm64's repos +RUN echo "# arm64 repositories" >> /etc/apt/sources.list +RUN echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted" >> /etc/apt/sources.list +RUN echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted" >> /etc/apt/sources.list +RUN echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy universe" >> /etc/apt/sources.list +RUN echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates universe" >> /etc/apt/sources.list +RUN echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy multiverse" >> /etc/apt/sources.list +RUN echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates multiverse" >> /etc/apt/sources.list +RUN echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse" >> /etc/apt/sources.list +RUN echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted" >> /etc/apt/sources.list +RUN echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security universe" >> /etc/apt/sources.list +RUN echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security multiverse" >> /etc/apt/sources.list + +RUN apt-get update && apt-get install -y build-essential git ca-certificates golang libudev-dev curl gnupg lsb-release curl gcc-arm* binutils-arm-linux-gnueabi crossbuild-essential-arm64 wget + +RUN echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null +RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +RUN apt-get update && apt-get install -y docker-ce-cli +# Replicate install of the same version of Golang that we are using in Github actions +RUN wget https://go.dev/dl/go1.20.2.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.20.2.linux-amd64.tar.gz && rm go1.20.2.linux-amd64.tar.gz +ENV GOROOT="/usr/local/go" +ENV PATH="/root/go/bin:$GOROOT/bin:${PATH}" +RUN go install github.com/goreleaser/goreleaser@v1.20.0 # supports golang 1.20 diff --git a/Makefile b/Makefile index bb31ec9c6..9d81af104 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ NAME=saml2aws ARCH=$(shell uname -m) -VERSION=2.28.0 +OS?=$(shell uname) ITERATION := 1 -GOLANGCI_VERSION = 1.32.0 -GORELEASER_VERSION = 0.157.0 +GOLANGCI_VERSION = 1.53.2 +GORELEASER := $(shell command -v goreleaser 2> /dev/null) SOURCE_FILES?=$$(go list ./... | grep -v /vendor/) TEST_PATTERN?=. @@ -12,33 +12,30 @@ TEST_OPTIONS?= BIN_DIR := $(CURDIR)/bin -ci: prepare test - -$(BIN_DIR)/golangci-lint: $(BIN_DIR)/golangci-lint-${GOLANGCI_VERSION} - @ln -sf golangci-lint-${GOLANGCI_VERSION} $(BIN_DIR)/golangci-lint -$(BIN_DIR)/golangci-lint-${GOLANGCI_VERSION}: - @curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | BINARY=golangci-lint bash -s -- v${GOLANGCI_VERSION} - @mv $(BIN_DIR)/golangci-lint $@ +# Choose the right config file for the OS +ifeq ($(OS),Darwin) + CONFIG_FILE?=$(CURDIR)/.goreleaser.macos-latest.yml +else ifeq ($(OS),Linux) + CONFIG_FILE?=$(CURDIR)/.goreleaser.ubuntu-20.04.yml +else + $(error Unsupported build OS: $(OS)) +endif -$(BIN_DIR)/goreleaser: $(BIN_DIR)/goreleaser-${GORELEASER_VERSION} - @ln -sf goreleaser-${GORELEASER_VERSION} $(BIN_DIR)/goreleaser -$(BIN_DIR)/goreleaser-${GORELEASER_VERSION}: - @curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | BINARY=goreleaser bash -s -- v${GORELEASER_VERSION} - @mv $(BIN_DIR)/goreleaser $@ +ci: prepare test mod: @go mod download @go mod tidy .PHONY: mod -lint: $(BIN_DIR)/golangci-lint +lint: @echo "--- lint all the things" - @$(BIN_DIR)/golangci-lint run ./... + @docker run --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:v$(GOLANGCI_VERSION) golangci-lint run -v .PHONY: lint -lint-fix: $(BIN_DIR)/golangci-lint +lint-fix: @echo "--- lint all the things" - @$(BIN_DIR)/golangci-lint run --fix ./... + @docker run --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:v$(GOLANGCI_VERSION) golangci-lint run -v --fix .PHONY: lint-fix fmt: lint-fix @@ -47,10 +44,18 @@ install: go install ./cmd/saml2aws .PHONY: mod -build: $(BIN_DIR)/goreleaser - $(BIN_DIR)/goreleaser build --snapshot --rm-dist +build: + +ifndef GORELEASER + $(error "goreleaser is not available please install and ensure it is on PATH") +endif + goreleaser build --snapshot --clean --config $(CONFIG_FILE) .PHONY: build +release-local: $(BIN_DIR)/goreleaser + goreleaser release --snapshot --rm-dist --config $(CONFIG_FILE) +.PHONY: release-local + clean: @rm -fr ./build .PHONY: clean @@ -64,3 +69,15 @@ test: @echo "--- test all the things" @go test -cover ./... .PHONY: test + +# It can be difficult to set up and test everything locally. Using this target you can build and run a docker container +# that has all the tools you need to build and test saml2aws. This is particularly useful on Mac as it allows the Linux +# and Docker builds to be tested. +# Note: By necessity, this target mounts the Docker socket into the container. This is a security risk and should not +# be used on a production system. +# Note: Files written by the container will be owned by root. This is a limitation of the Docker socket mount. +# You may need to run `docker run --privileged --rm tonistiigi/binfmt --install all` to enable the buildx plugin. +docker-build-environment: + docker build --platform=amd64 -t saml2aws/build -f Dockerfile.build . + docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock -e BUILDX_CONFIG=$(PWD)/.buildtemp -e GOPATH=$(PWD)/.buildtemp -e GOTMPDIR=$(PWD)/.buildtemp -e GOCACHE=$(PWD)/.buildtemp/.cache -e GOENV=$(PWD)/.buildtemp/env -v $(PWD):$(PWD) -w $(PWD) saml2aws/build:latest +.PHONY: docker-build-environment \ No newline at end of file diff --git a/README.md b/README.md index 4fda2277c..765d9bf74 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# saml2aws [![GitHub Actions status](https://github.com/Versent/saml2aws/workflows/Go/badge.svg?branch=master)](https://github.com/Versent/saml2aws/actions?query=workflow%3AGo) [![Build status - Windows](https://ci.appveyor.com/api/projects/status/ptpi18kci16o4i82/branch/master?svg=true)](https://ci.appveyor.com/project/davidobrien1985/saml2aws/branch/master) +# saml2aws + +[![GitHub Actions status](https://github.com/Versent/saml2aws/workflows/Go/badge.svg?branch=master)](https://github.com/Versent/saml2aws/actions?query=workflow%3AGo) [![Build status - Windows](https://ci.appveyor.com/api/projects/status/ptpi18kci16o4i82/branch/master?svg=true)](https://ci.appveyor.com/project/davidobrien1985/saml2aws/branch/master) +[![codecov](https://codecov.io/gh/Versent/saml2aws/branch/master/graph/badge.svg)](https://codecov.io/gh/Versent/saml2aws) CLI tool which enables you to login and retrieve [AWS](https://aws.amazon.com/) temporary credentials using with [ADFS](https://msdn.microsoft.com/en-us/library/bb897402.aspx) or [PingFederate](https://www.pingidentity.com/en/products/pingfederate.html) Identity Providers. @@ -18,25 +21,47 @@ The process goes something like this: ## Table of Contents -- [Table of Contents](#table-of-contents) -- [Requirements](#requirements) -- [Caveats](#caveats) -- [Install](#install) +- [saml2aws](#saml2aws) + - [Table of Contents](#table-of-contents) + - [Requirements](#requirements) + - [Caveats](#caveats) + - [Install](#install) - [OSX](#osx) - [Windows](#windows) - [Linux](#linux) -- [Autocomplete](#autocomplete) -- [Dependency Setup](#dependency-setup) -- [Usage](#usage) + - [Using Make](#using-make) + - [Arch Linux and its derivatives](#arch-linux-and-its-derivatives) + - [Void Linux](#void-linux) + - [Autocomplete](#autocomplete) + - [Bash](#bash) + - [Zsh](#zsh) + - [Dependency Setup](#dependency-setup) + - [Usage](#usage) - [`saml2aws script`](#saml2aws-script) + - [`saml2aws exec`](#saml2aws-exec) - [Configuring IDP Accounts](#configuring-idp-accounts) -- [Example](#example) -- [Advanced Configuration](#advanced-configuration) - - [Dev Account Setup](#dev-account-setup) - - [Test Account Setup](#test-account-setup) -- [Building](#building) -- [Environment vars](#environment-vars) -- [Provider Specific Documentation](#provider-specific-documentation) + - [Example](#example) + - [Advanced Configuration](#advanced-configuration) + - [Windows Subsystem Linux (WSL) Configuration](#windows-subsystem-linux-wsl-configuration) + - [Option 1: Disable Keychain](#option-1-disable-keychain) + - [Option 2: Configure Pass to be the default keyring](#option-2-configure-pass-to-be-the-default-keyring) + - [Configuring Multiple Accounts](#configuring-multiple-accounts) + - [Dev Account Setup](#dev-account-setup) + - [Test Account Setup](#test-account-setup) + - [Advanced Configuration (Multiple AWS account access but SAML authenticate against a single 'SSO' AWS account)](#advanced-configuration-multiple-aws-account-access-but-saml-authenticate-against-a-single-sso-aws-account) + - [Advanced Configuration - additional parameters](#advanced-configuration---additional-parameters) + - [Building](#building) + - [macOS](#macos) + - [Linux](#linux-1) + - [Environment vars](#environment-vars) + - [Provider Specific Documentation](#provider-specific-documentation) +- [Dependencies](#dependencies) +- [Releasing](#releasing) +- [Debugging Issues with IDPs](#debugging-issues-with-idps) +- [Using saml2aws as credential process](#using-saml2aws-as-credential-process) +- [Caching the saml2aws SAML assertion for immediate reuse](#caching-the-saml2aws-saml-assertion-for-immediate-reuse) +- [Okta Sessions](#okta-sessions) +- [License](#license) ## Requirements @@ -52,7 +77,7 @@ The process goes something like this: * [Akamai](pkg/provider/akamai/README.md) * OneLogin * NetIQ - * Browser, this uses [playwright-go](github.com/mxschmitt/playwright-go) to run a sandbox chromium window. + * Browser, this uses [playwright-go](github.com/playwright-community/playwright-go) to run a sandbox chromium window. * [Auth0](pkg/provider/auth0/README.md) NOTE: Currently, MFA not supported * AWS SAML Provider configured @@ -62,6 +87,7 @@ Aside from Okta, most of the providers in this project are using screen scraping 1. AWS defaults to session tokens being issued with a duration of up to 3600 seconds (1 hour), this can now be configured as per [Enable Federated API Access to your AWS Resources for up to 12 hours Using IAM Roles](https://aws.amazon.com/blogs/security/enable-federated-api-access-to-your-aws-resources-for-up-to-12-hours-using-iam-roles/) and `--session-duration` flag. 2. Every SAML provider is different, the login process, MFA support is pluggable and therefore some work may be needed to integrate with your identity server +3. By default, the temporary security credentials returned **do not support SigV4A**. If you need SigV4A support then you must set the `AWS_STS_REGIONAL_ENDPOINTS` enviornment variable to `regional` when calling `saml2aws` so that [aws-sdk-go](https://github.com/aws/aws-sdk-go) uses a regional STS endpoint instead of the global one. See the note at the bottom of [Signing AWS API requests](https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html#signature-versions) and [AWS STS Regionalized endpoints](https://docs.aws.amazon.com/sdkref/latest/guide/feature-sts-regionalized-endpoints.html). ## Install @@ -88,12 +114,30 @@ saml2aws --version While brew is available for Linux you can also run the following without using a package manager. ``` +mkdir -p ~/.local/bin CURRENT_VERSION=$(curl -Ls https://api.github.com/repos/Versent/saml2aws/releases/latest | grep 'tag_name' | cut -d'v' -f2 | cut -d'"' -f1) -wget -c https://github.com/Versent/saml2aws/releases/download/v${CURRENT_VERSION}/saml2aws_${CURRENT_VERSION}_linux_amd64.tar.gz -O - | tar -xzv -C ~/.local/bin +wget -c "https://github.com/Versent/saml2aws/releases/download/v${CURRENT_VERSION}/saml2aws_${CURRENT_VERSION}_linux_amd64.tar.gz" -O - | tar -xzv -C ~/.local/bin chmod u+x ~/.local/bin/saml2aws hash -r saml2aws --version ``` +If U2F support is required then there are separate builds for this - use the following download URL instead: +``` +wget -c "https://github.com/Versent/saml2aws/releases/download/v${CURRENT_VERSION}/saml2aws-u2f_${CURRENT_VERSION}_linux_amd64.tar.gz" -O - | tar -xzv -C ~/.local/bin +``` + +#### Using Make + +You will need [Go Tools](https://golang.org/doc/install) (you can check your package maintainer as well) installed and the [Go Lint tool](https://github.com/alecthomas/gometalinter) + +Clone this repo to your `$GOPATH/src` directory + +Now you can install by running + +``` +make +make install +``` #### [Arch Linux](https://archlinux.org/) and its derivatives @@ -151,6 +195,10 @@ Flags: -a, --idp-account="default" The name of the configured IDP account. (env: SAML2AWS_IDP_ACCOUNT) --idp-provider=IDP-PROVIDER The configured IDP provider. (env: SAML2AWS_IDP_PROVIDER) + --browser-type=BROWSER-TYPE + The browser type to use when IDP provider is set to 'Browser'. if not set 'chromium' will be used. (env: SAML2AWS_BROWSER_TYPE) + --browser-executable-path=BROWSER-EXECUTABLE-PATH + The browser full path when IDP provider is set to 'Browser'. If set, no browser download will be performed and the executable path will be used instead. (env: SAML2AWS_BROWSER_EXECUTABLE_PATH) --mfa=MFA The name of the mfa. (env: SAML2AWS_MFA) -s, --skip-verify Skip verification of server certificate. (env: SAML2AWS_SKIP_VERIFY) --url=URL The URL of the SAML IDP server used to login. (env: SAML2AWS_URL) @@ -178,6 +226,8 @@ Commands: --client-secret=CLIENT-SECRET OneLogin client secret, used to generate API access token. (env: ONELOGIN_CLIENT_SECRET) --subdomain=SUBDOMAIN OneLogin subdomain of your company account. (env: ONELOGIN_SUBDOMAIN) + --mfa-ip-address=MFA-IP-ADDRESS + IP address whitelisting defined in OneLogin MFA policies. (env: ONELOGIN_MFA_IP_ADDRESS) -p, --profile=PROFILE The AWS profile to save the temporary credentials. (env: SAML2AWS_PROFILE) --resource-id=RESOURCE-ID F5APM SAML resource ID of your company account. (env: SAML2AWS_F5APM_RESOURCE_ID) --config=CONFIG Path/filename of saml2aws config file (env: SAML2AWS_CONFIGFILE) @@ -191,16 +241,19 @@ Commands: -p, --profile=PROFILE The AWS profile to save the temporary credentials. (env: SAML2AWS_PROFILE) --duo-mfa-option=DUO-MFA-OPTION - The MFA option you want to use to authenticate with + The MFA option you want to use to authenticate (supported providers: okta)(env: SAML_DUO_MFA_OPTION) --client-id=CLIENT-ID OneLogin client id, used to generate API access token. (env: ONELOGIN_CLIENT_ID) --client-secret=CLIENT-SECRET OneLogin client secret, used to generate API access token. (env: ONELOGIN_CLIENT_SECRET) + --mfa-ip-address=MFA-IP-ADDRESS + IP address whitelisting defined in OneLogin MFA policies. (env: ONELOGIN_MFA_IP_ADDRESS) --force Refresh credentials even if not expired. --credential-process Enables AWS Credential Process support by outputting credentials to STDOUT in a JSON message. --credentials-file=CREDENTIALS-FILE The file that will cache the credentials retrieved from AWS. When not specified, will use the default AWS credentials file location. (env: SAML2AWS_CREDENTIALS_FILE) --cache-saml Caches the SAML response (env: SAML2AWS_CACHE_SAML) --cache-file=CACHE-FILE The location of the SAML cache file (env: SAML2AWS_SAML_CACHE_FILE) + --download-browser-driver Automatically download browsers for Browser IDP. (env: SAML2AWS_AUTO_BROWSER_DOWNLOAD) --disable-sessions Do not use Okta sessions. Uses Okta sessions by default. (env: SAML2AWS_OKTA_DISABLE_SESSIONS) --disable-remember-device Do not remember Okta MFA device. Remembers MFA device by default. (env: SAML2AWS_OKTA_DISABLE_REMEMBER_DEVICE) @@ -234,7 +287,7 @@ Commands: Emit a script that will export environment variables. -p, --profile=PROFILE The AWS profile to save the temporary credentials. (env: SAML2AWS_PROFILE) - --shell=bash Type of shell environment. Options include: bash, powershell, fish, env + --shell=bash Type of shell environment. Options include: bash, /bin/sh, powershell, fish, env --credentials-file=CREDENTIALS-FILE The file that will cache the credentials retrieved from AWS. When not specified, will use the default AWS credentials file location. (env: SAML2AWS_CREDENTIALS_FILE) @@ -254,7 +307,7 @@ export AWS_CREDENTIAL_EXPIRATION="2016-09-04T38:27:00Z00:00" SAML2AWS_PROFILE=saml ``` -Powershell, and fish shells are supported as well. +Powershell, sh and fish shells are supported as well. Env is useful for all AWS SDK compatible tools that can source an env file. It is a powerful combo with docker and the `--env-file` parameter. If you use `eval $(saml2aws script)` frequently, you may want to create a alias for it: @@ -385,10 +438,65 @@ To use this credential, call the AWS CLI with the --profile option (e.g. aws --p ``` ## Advanced Configuration +### Windows Subsystem Linux (WSL) Configuration +If you are using WSL1 or WSL2, you might get the following error when attempting to save the credentials into the keychain -Configuring multiple accounts with custom role and profile in `~/.aws/config` with goal being isolation between infra code when deploying to these environments. This setup assumes you're using separate roles and probably AWS accounts for `dev` and `test` and is designed to help operations staff avoid accidentally deploying to the wrong AWS account in complex environments. Note that this method configures SAML authentication to each AWS account directly (in this case different AWS accounts). In the example below, separate authentication values are configured for AWS accounts 'profile=customer-dev/awsAccount=was 121234567890' and 'profile=customer-test/awsAccount=121234567891' +``` + No such interface “org.freedesktop.DBus.Properties” on object at path / +``` + +This happens because the preferred keyring back-end - uses the `gnome-keyring` by default - which requires X11 - and if you are not using Windows 11 with support for Linux GUI applications - this can be difficult without [configuring a X11 forward](https://stackoverflow.com/questions/61110603/how-to-set-up-working-x11-forwarding-on-wsl2). + +There are 2 preferred approaches to workaround this issue: + +#### Option 1: Disable Keychain +You can apply the `--disable-keychain` flag when using both the `configure` and `login` commands. Using this flag means that your credentials (such as your password to your IDP, or in the case of Okta the Okta Session Token) will not save to your keychain - and be skipped entierly. This means you will be required to enter your username and password each time you invoke the `login` command. + +#### Option 2: Configure Pass to be the default keyring +There are a few steps involved with this option - however this option will save your credentials (such as your password to your IDP, and session tokens etc) into the `pass`[https://www.passwordstore.org/] keyring. The `pass` keyring is the standard Unix password manager. This option was *heavily inspired* by a similar issue in [aws-vault](https://github.com/99designs/aws-vault/issues/683) + +To configure pass to be the default keyring the following steps will need to be completed (assuming you are using Ubuntu 20.04 LTS): + +1. Install the pass backend and update gnupg, which encrypts passwords +```bash +sudo apt-get update && sudo apt-get install -y pass gnupg +``` + +2. Generate a key with gpg (gnupg) and take note of your public key +```bash +gpg --gen-key +``` + +The output of the gpg command will output the something similar to the following: +``` +public and secret key created and signed. + +pub rsa3072 2021-04-22 [SC] [expires: 2023-04-22] + 844E426A53A64C2A916CBD1F522014D5FDBF6E3D +uid Meir Gabay +sub rsa3072 2021-04-22 [E] [expires: 2023-04-22] +``` + +3. Create a storage key in pass from the previously generated public (pub) key +```bash +pass init +``` +during the `init` process you'll be requested to enter the passphrase provided in step 2 + +4. Now, configure `saml2aws` to use the `pass` keyring. This can be done by setting the `SAML2AWS_KEYRING_BACKEND` environment variable to be `pass`. You'll need to also set the `GPG_TTY` to your current tty which means you can set the variable to `"$( tty )"` + +which means the following can be added into your profile +``` +export SAML2AWS_KEYRING_BACKEND=pass +export GPG_TTY="$( tty )" +``` + +5. Profit! Now when you run login/configure commands - you'll be promoted once to enter your passphrase - and your credentials will be saved into your keyring! -### Dev Account Setup + +### Configuring Multiple Accounts +Configuring multiple accounts with custom role and profile in `~/.aws/config` with goal being isolation between infra code when deploying to these environments. This setup assumes you're using separate roles and probably AWS accounts for `dev` and `test` and is designed to help operations staff avoid accidentally deploying to the wrong AWS account in complex environments. Note that this method configures SAML authentication to each AWS account directly (in this case different AWS accounts). In the example below, separate authentication values are configured for AWS accounts 'profile=customer-dev/awsAccount=was 121234567890' and 'profile=customer-test/awsAccount=121234567891' +#### Dev Account Setup To setup the dev account run the following and enter URL, username and password, and assign a standard role to be automatically selected on login. @@ -415,7 +523,7 @@ region = us-east-1 To use this you will need to export `AWS_DEFAULT_PROFILE=customer-dev` environment variable to target `dev`. -### Test Account Setup +#### Test Account Setup To setup the test account run the following and enter URL, username and password. @@ -442,6 +550,18 @@ region = us-east-1 To use this you will need to export `AWS_DEFAULT_PROFILE=customer-test` environment variable to target `test`. +### Playwright Browser Drivers for Browser IDP + +If you are using the Browser Identity Provider, on first invocation of `saml2aws login` you need to remember to install +the browser drivers in order for playwright-go to work. Otherwise you will see the following error message: + +`Error authenticating to IDP.: could not start driver: fork/exec ... no such file or directory` + +To install the drivers, you can: +* Pass `--download-browser-driver` to `saml2aws login` +* Set in your shell environment `SAML2AWS_AUTO_BROWSER_DOWNLOAD=true` +* Set `download_browser_driver = true` in your saml2aws config file, i.e. `~/.saml2aws` + ## Advanced Configuration (Multiple AWS account access but SAML authenticate against a single 'SSO' AWS account) Example: @@ -573,6 +693,8 @@ region = us-east-1 ``` ## Building +### macOS + To build this software on osx clone to the repo to `$GOPATH/src/github.com/versent/saml2aws` and ensure you have `$GOPATH/bin` in your `$PATH`. ``` @@ -597,6 +719,26 @@ Before raising a PR please run the linter. make lint-fix ``` +### Linux + +To build this software on Debian/Ubuntu, you need to install a build dependency: + +``` +sudo apt install libudev-dev +``` + +You also need [GoReleaser](https://github.com/goreleaser/goreleaser) installed, and the binary (or a symlink) in `bin/goreleaser`. + +``` +ln -s $(command -v goreleaser) bin/goreleaser +``` + +Then you can build: + +``` +make build +``` + ## Environment vars The exec sub command will export the following environment variables. diff --git a/aws_account.go b/aws_account.go index bdf7e83d6..ff28c3abf 100644 --- a/aws_account.go +++ b/aws_account.go @@ -3,7 +3,7 @@ package saml2aws import ( "bytes" "fmt" - "io/ioutil" + "io" "net/http" "net/url" @@ -24,7 +24,7 @@ func ParseAWSAccounts(audience string, samlAssertion string) ([]*AWSAccount, err return nil, errors.Wrap(err, "error retrieving AWS login form") } - data, err := ioutil.ReadAll(res.Body) + data, err := io.ReadAll(res.Body) if err != nil { return nil, errors.Wrap(err, "error retrieving AWS login body") } diff --git a/aws_account_test.go b/aws_account_test.go index 9ea4ec11c..b5c86c43b 100644 --- a/aws_account_test.go +++ b/aws_account_test.go @@ -1,14 +1,14 @@ package saml2aws import ( - "io/ioutil" + "os" "testing" "github.com/stretchr/testify/assert" ) func TestExtractAWSAccounts(t *testing.T) { - data, err := ioutil.ReadFile("testdata/saml.html") + data, err := os.ReadFile("testdata/saml.html") assert.Nil(t, err) accounts, err := ExtractAWSAccounts(data) diff --git a/aws_role.go b/aws_role.go index c843f9b8f..60ff2246b 100644 --- a/aws_role.go +++ b/aws_role.go @@ -2,6 +2,7 @@ package saml2aws import ( "fmt" + "regexp" "strings" ) @@ -29,7 +30,8 @@ func ParseAWSRoles(roles []string) ([]*AWSRole, error) { } func parseRole(role string) (*AWSRole, error) { - tokens := strings.Split(role, ",") + r, _ := regexp.Compile("arn:([^:\n]*):([^:\n]*):([^:\n]*):([^:\n]*):(([^:/\n]*)[:/])?([^:,\n]*)") + tokens := r.FindAllString(role, -1) if len(tokens) != 2 { return nil, fmt.Errorf("Invalid role string only %d tokens", len(tokens)) diff --git a/choco/VERIFICATION.txt b/choco/VERIFICATION.txt index 02109c0a6..0b4e283e8 100644 --- a/choco/VERIFICATION.txt +++ b/choco/VERIFICATION.txt @@ -2,7 +2,7 @@ VERIFICATION Verification is intended to assist the Chocolatey moderators and community in verifying that this package's contents are trustworthy. -The installer has been automatically built from source whch can be found on +The installer has been automatically built from source which can be found on and can be verified like this: 1. Download the release version from @@ -11,4 +11,4 @@ and can be verified like this: - Use chocolatey utility 'checksum.exe' Compare the checksums there with the checksum of the local binary in C:\ProgramData\Chocolatey\lib\saml2aws\src\saml2aws.exe -File 'LICENSE.txt' is obtained from . \ No newline at end of file +File 'LICENSE.txt' is obtained from . diff --git a/cmd/saml2aws/commands/configure.go b/cmd/saml2aws/commands/configure.go index feddd6f64..a8f9fc7c2 100644 --- a/cmd/saml2aws/commands/configure.go +++ b/cmd/saml2aws/commands/configure.go @@ -88,7 +88,7 @@ func storeCredentials(configFlags *flags.CommonFlags, account *cfg.IDPAccount) e } if account.Provider == onelogin.ProviderName { if configFlags.ClientID == "" || configFlags.ClientSecret == "" { - log.Println("OneLogin provider requires --client_id and --client_secret flags to be set.") + log.Println("OneLogin provider requires --client-id and --client-secret flags to be set.") os.Exit(1) } if err := credentials.SaveCredentials(path.Join(account.URL, OneLoginOAuthPath), configFlags.ClientID, configFlags.ClientSecret); err != nil { diff --git a/cmd/saml2aws/commands/console.go b/cmd/saml2aws/commands/console.go index cf11bf0d3..70e1f20d6 100644 --- a/cmd/saml2aws/commands/console.go +++ b/cmd/saml2aws/commands/console.go @@ -3,7 +3,7 @@ package commands import ( "encoding/json" "fmt" - "io/ioutil" + "io" "log" "net/http" "net/url" @@ -136,7 +136,7 @@ func federatedLogin(creds *awsconfig.AWSCredentials, consoleFlags *flags.Console } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return err } diff --git a/cmd/saml2aws/commands/login.go b/cmd/saml2aws/commands/login.go index eb80280e1..10804d8e4 100644 --- a/cmd/saml2aws/commands/login.go +++ b/cmd/saml2aws/commands/login.go @@ -219,6 +219,19 @@ func resolveLoginDetails(account *cfg.IDPAccount, loginFlags *flags.LoginExecFla loginDetails.ClientSecret = loginFlags.CommonFlags.ClientSecret } + // if you supply an mfa_ip_address in a flag or an IDP account it takes precedence + if account.MFAIPAddress != "" { + loginDetails.MFAIPAddress = account.MFAIPAddress + } else if loginFlags.CommonFlags.MFAIPAddress != "" { + loginDetails.MFAIPAddress = loginFlags.CommonFlags.MFAIPAddress + } + + if loginFlags.DownloadBrowser { + loginDetails.DownloadBrowser = loginFlags.DownloadBrowser + } else if account.DownloadBrowser { + loginDetails.DownloadBrowser = account.DownloadBrowser + } + // log.Printf("loginDetails %+v", loginDetails) // if skip prompt was passed just pass back the flag values diff --git a/cmd/saml2aws/commands/login_darwin.go b/cmd/saml2aws/commands/login_darwin.go index 14b4d9bbe..c2d036354 100644 --- a/cmd/saml2aws/commands/login_darwin.go +++ b/cmd/saml2aws/commands/login_darwin.go @@ -1,3 +1,4 @@ +//go:build darwin && cgo // +build darwin,cgo package commands diff --git a/cmd/saml2aws/commands/login_linux.go b/cmd/saml2aws/commands/login_linux.go index b051a0f79..94a4d30fb 100644 --- a/cmd/saml2aws/commands/login_linux.go +++ b/cmd/saml2aws/commands/login_linux.go @@ -1,12 +1,19 @@ package commands import ( + "os" + "github.com/versent/saml2aws/v2/helper/credentials" "github.com/versent/saml2aws/v2/helper/linuxkeyring" + "github.com/versent/saml2aws/v2/pkg/cfg" ) func init() { - if keyringHelper, err := linuxkeyring.NewKeyringHelper(); err == nil { + c := linuxkeyring.Configuration{ + Backend: os.Getenv(cfg.KeyringBackEnvironmentVariableName), + } + + if keyringHelper, err := linuxkeyring.NewKeyringHelper(c); err == nil { credentials.CurrentHelper = keyringHelper } } diff --git a/cmd/saml2aws/commands/login_test.go b/cmd/saml2aws/commands/login_test.go index 0e29f3640..bca0442cf 100644 --- a/cmd/saml2aws/commands/login_test.go +++ b/cmd/saml2aws/commands/login_test.go @@ -15,7 +15,7 @@ import ( func TestResolveLoginDetailsWithFlags(t *testing.T) { - commonFlags := &flags.CommonFlags{URL: "https://id.example.com", Username: "wolfeidau", Password: "testtestlol", MFAToken: "123456", SkipPrompt: true} + commonFlags := &flags.CommonFlags{URL: "https://id.example.com", Username: "wolfeidau", Password: "testtestlol", MFAIPAddress: "127.0.0.1", MFAToken: "123456", SkipPrompt: true} loginFlags := &flags.LoginExecFlags{CommonFlags: commonFlags} idpa := &cfg.IDPAccount{ @@ -27,7 +27,7 @@ func TestResolveLoginDetailsWithFlags(t *testing.T) { loginDetails, err := resolveLoginDetails(idpa, loginFlags) assert.Empty(t, err) - assert.Equal(t, &creds.LoginDetails{Username: "wolfeidau", Password: "testtestlol", URL: "https://id.example.com", MFAToken: "123456"}, loginDetails) + assert.Equal(t, &creds.LoginDetails{Username: "wolfeidau", Password: "testtestlol", URL: "https://id.example.com", MFAToken: "123456", MFAIPAddress: "127.0.0.1"}, loginDetails) } func TestOktaResolveLoginDetailsWithFlags(t *testing.T) { diff --git a/cmd/saml2aws/commands/script.go b/cmd/saml2aws/commands/script.go index 4a736e6b4..0f35b3dbd 100644 --- a/cmd/saml2aws/commands/script.go +++ b/cmd/saml2aws/commands/script.go @@ -20,6 +20,14 @@ export SAML2AWS_PROFILE={{ .ProfileName }} export AWS_CREDENTIAL_EXPIRATION={{ .Expires.Format "2006-01-02T15:04:05Z07:00" }} ` +const shTmpl = `export AWS_ACCESS_KEY_ID={{ .AWSAccessKey }} +export AWS_SECRET_ACCESS_KEY={{ .AWSSecretKey }} +export AWS_SESSION_TOKEN={{ .AWSSessionToken }} +export AWS_SECURITY_TOKEN={{ .AWSSecurityToken }} +export SAML2AWS_PROFILE={{ .ProfileName }} +export AWS_CREDENTIAL_EXPIRATION={{ .Expires.Format "2006-01-02T15:04:05Z07:00" }} +` + const fishTmpl = `set -gx AWS_ACCESS_KEY_ID {{ .AWSAccessKey }} set -gx AWS_SECRET_ACCESS_KEY {{ .AWSSecretKey }} set -gx AWS_SESSION_TOKEN {{ .AWSSessionToken }} @@ -99,6 +107,8 @@ func buildTmpl(shell string, data interface{}) (string, error) { switch shell { case "bash": t, err = t.Parse(bashTmpl) + case "/bin/sh": + t, err = t.Parse(shTmpl) case "powershell": t, err = t.Parse(powershellTmpl) case "fish": diff --git a/cmd/saml2aws/commands/script_test.go b/cmd/saml2aws/commands/script_test.go index 0e44073c4..9d458584f 100644 --- a/cmd/saml2aws/commands/script_test.go +++ b/cmd/saml2aws/commands/script_test.go @@ -25,7 +25,40 @@ func TestBuildTmplBash(t *testing.T) { } st, err := buildTmpl("bash", data) - assert.ErrorIs(t, err, nil) + assert.Nil(t, err) + + expected := []string{ + "export AWS_ACCESS_KEY_ID=access_key", + "export AWS_SECRET_ACCESS_KEY=secret_key", + "export AWS_SESSION_TOKEN=session_token", + "export AWS_SECURITY_TOKEN=security_token", + "export SAML2AWS_PROFILE=test_profile", + } + + for _, test_string := range expected { + assert.Contains(t, st, test_string) + } + +} + +func TestBuildTmplSh(t *testing.T) { + + data := struct { + ProfileName string + *awsconfig.AWSCredentials + }{ + "test_profile", + &awsconfig.AWSCredentials{ + AWSSecretKey: "secret_key", + AWSAccessKey: "access_key", + AWSSessionToken: "session_token", + AWSSecurityToken: "security_token", + Expires: time.Now(), + }, + } + + st, err := buildTmpl("/bin/sh", data) + assert.Nil(t, err) expected := []string{ "export AWS_ACCESS_KEY_ID=access_key", @@ -58,7 +91,7 @@ func TestBuildTmplFish(t *testing.T) { } st, err := buildTmpl("fish", data) - assert.ErrorIs(t, err, nil) + assert.Nil(t, err) expected := []string{ "set -gx AWS_ACCESS_KEY_ID access_key", @@ -91,7 +124,7 @@ func TestBuildTmplEnv(t *testing.T) { } st, err := buildTmpl("env", data) - assert.ErrorIs(t, err, nil) + assert.Nil(t, err) expected := []string{ "AWS_ACCESS_KEY_ID=access_key", diff --git a/cmd/saml2aws/main.go b/cmd/saml2aws/main.go index d6ee181e3..1085bb69f 100644 --- a/cmd/saml2aws/main.go +++ b/cmd/saml2aws/main.go @@ -2,7 +2,7 @@ package main import ( "crypto/tls" - "io/ioutil" + "io" "log" "net/http" "os" @@ -12,6 +12,7 @@ import ( "github.com/sirupsen/logrus" "github.com/versent/saml2aws/v2/cmd/saml2aws/commands" "github.com/versent/saml2aws/v2/pkg/flags" + "github.com/versent/saml2aws/v2/pkg/prompter" ) var ( @@ -46,6 +47,7 @@ func buildCmdList(s kingpin.Settings) (target *[]string) { func main() { log.SetOutput(os.Stderr) + prompter.SetOutputWriter(os.Stderr) log.SetFlags(0) logrus.SetOutput(os.Stderr) @@ -70,6 +72,8 @@ func main() { app.Flag("config", "Path/filename of saml2aws config file (env: SAML2AWS_CONFIGFILE)").Envar("SAML2AWS_CONFIGFILE").StringVar(&commonFlags.ConfigFile) app.Flag("idp-account", "The name of the configured IDP account. (env: SAML2AWS_IDP_ACCOUNT)").Envar("SAML2AWS_IDP_ACCOUNT").Short('a').Default("default").StringVar(&commonFlags.IdpAccount) app.Flag("idp-provider", "The configured IDP provider. (env: SAML2AWS_IDP_PROVIDER)").Envar("SAML2AWS_IDP_PROVIDER").EnumVar(&commonFlags.IdpProvider, "Akamai", "AzureAD", "ADFS", "ADFS2", "Browser", "GoogleApps", "Ping", "JumpCloud", "Okta", "OneLogin", "PSU", "KeyCloak", "F5APM", "Shibboleth", "ShibbolethECP", "NetIQ", "Auth0") + app.Flag("browser-type", "The configured browser type when the IDP provider is set to Browser. if not set 'chromium' will be used. (env: SAML2AWS_BROWSER_TYPE)").Envar("SAML2AWS_BROWSER_TYPE").EnumVar(&commonFlags.BrowserType, "chromium", "firefox", "webkit", "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", "msedge-canary") + app.Flag("browser-executable-path", "The configured browser full path when the IDP provider is set to Browser. If set, no browser download will be performed and the executable path will be used instead. (env: SAML2AWS_BROWSER_EXECUTABLE_PATH)").Envar("SAML2AWS_BROWSER_EXECUTABLE_PATH").StringVar(&commonFlags.BrowserExecutablePath) app.Flag("mfa", "The name of the mfa. (env: SAML2AWS_MFA)").Envar("SAML2AWS_MFA").StringVar(&commonFlags.MFA) app.Flag("skip-verify", "Skip verification of server certificate. (env: SAML2AWS_SKIP_VERIFY)").Envar("SAML2AWS_SKIP_VERIFY").Short('s').BoolVar(&commonFlags.SkipVerify) app.Flag("url", "The URL of the SAML IDP server used to login. (env: SAML2AWS_URL)").Envar("SAML2AWS_URL").StringVar(&commonFlags.URL) @@ -90,6 +94,7 @@ func main() { cmdConfigure.Flag("client-id", "OneLogin client id, used to generate API access token. (env: ONELOGIN_CLIENT_ID)").Envar("ONELOGIN_CLIENT_ID").StringVar(&commonFlags.ClientID) cmdConfigure.Flag("client-secret", "OneLogin client secret, used to generate API access token. (env: ONELOGIN_CLIENT_SECRET)").Envar("ONELOGIN_CLIENT_SECRET").StringVar(&commonFlags.ClientSecret) cmdConfigure.Flag("subdomain", "OneLogin subdomain of your company account. (env: ONELOGIN_SUBDOMAIN)").Envar("ONELOGIN_SUBDOMAIN").StringVar(&commonFlags.Subdomain) + cmdConfigure.Flag("mfa-ip-address", "IP address whitelisting defined in OneLogin MFA policies. (env: ONELOGIN_MFA_IP_ADDRESS)").Envar("ONELOGIN_MFA_IP_ADDRESS").StringVar(&commonFlags.MFAIPAddress) cmdConfigure.Flag("profile", "The AWS profile to save the temporary credentials. (env: SAML2AWS_PROFILE)").Envar("SAML2AWS_PROFILE").Short('p').StringVar(&commonFlags.Profile) cmdConfigure.Flag("resource-id", "F5APM SAML resource ID of your company account. (env: SAML2AWS_F5APM_RESOURCE_ID)").Envar("SAML2AWS_F5APM_RESOURCE_ID").StringVar(&commonFlags.ResourceID) cmdConfigure.Flag("credentials-file", "The file that will cache the credentials retrieved from AWS. When not specified, will use the default AWS credentials file location. (env: SAML2AWS_CREDENTIALS_FILE)").Envar("SAML2AWS_CREDENTIALS_FILE").StringVar(&commonFlags.CredentialsFile) @@ -104,14 +109,16 @@ func main() { loginFlags := new(flags.LoginExecFlags) loginFlags.CommonFlags = commonFlags cmdLogin.Flag("profile", "The AWS profile to save the temporary credentials. (env: SAML2AWS_PROFILE)").Short('p').Envar("SAML2AWS_PROFILE").StringVar(&commonFlags.Profile) - cmdLogin.Flag("duo-mfa-option", "The MFA option you want to use to authenticate with").Envar("SAML2AWS_DUO_MFA_OPTION").EnumVar(&loginFlags.DuoMFAOption, "Passcode", "Phone Call", "Duo Push") + cmdLogin.Flag("duo-mfa-option", "The MFA option you want to use to authenticate with (supported providers: okta)").Envar("SAML2AWS_DUO_MFA_OPTION").EnumVar(&loginFlags.DuoMFAOption, "Passcode", "Duo Push") cmdLogin.Flag("client-id", "OneLogin client id, used to generate API access token. (env: ONELOGIN_CLIENT_ID)").Envar("ONELOGIN_CLIENT_ID").StringVar(&commonFlags.ClientID) cmdLogin.Flag("client-secret", "OneLogin client secret, used to generate API access token. (env: ONELOGIN_CLIENT_SECRET)").Envar("ONELOGIN_CLIENT_SECRET").StringVar(&commonFlags.ClientSecret) + cmdLogin.Flag("mfa-ip-address", "IP address whitelisting defined in OneLogin MFA policies. (env: ONELOGIN_MFA_IP_ADDRESS)").Envar("ONELOGIN_MFA_IP_ADDRESS").StringVar(&commonFlags.MFAIPAddress) cmdLogin.Flag("force", "Refresh credentials even if not expired.").BoolVar(&loginFlags.Force) cmdLogin.Flag("credential-process", "Enables AWS Credential Process support by outputting credentials to STDOUT in a JSON message.").BoolVar(&loginFlags.CredentialProcess) cmdLogin.Flag("credentials-file", "The file that will cache the credentials retrieved from AWS. When not specified, will use the default AWS credentials file location. (env: SAML2AWS_CREDENTIALS_FILE)").Envar("SAML2AWS_CREDENTIALS_FILE").StringVar(&commonFlags.CredentialsFile) cmdLogin.Flag("cache-saml", "Caches the SAML response (env: SAML2AWS_CACHE_SAML)").Envar("SAML2AWS_CACHE_SAML").BoolVar(&commonFlags.SAMLCache) cmdLogin.Flag("cache-file", "The location of the SAML cache file (env: SAML2AWS_SAML_CACHE_FILE)").Envar("SAML2AWS_SAML_CACHE_FILE").StringVar(&commonFlags.SAMLCacheFile) + cmdLogin.Flag("download-browser-driver", "Automatically download browsers for Browser IDP. (env: SAML2AWS_AUTO_BROWSER_DOWNLOAD)").Envar("SAML2AWS_AUTO_BROWSER_DOWNLOAD").BoolVar(&loginFlags.DownloadBrowser) cmdLogin.Flag("disable-sessions", "Do not use Okta sessions. Uses Okta sessions by default. (env: SAML2AWS_OKTA_DISABLE_SESSIONS)").Envar("SAML2AWS_OKTA_DISABLE_SESSIONS").BoolVar(&commonFlags.DisableSessions) cmdLogin.Flag("disable-remember-device", "Do not remember Okta MFA device. Remembers MFA device by default. (env: SAML2AWS_OKTA_DISABLE_REMEMBER_DEVICE)").Envar("SAML2AWS_OKTA_DISABLE_REMEMBER_DEVICE").BoolVar(&commonFlags.DisableRememberDevice) @@ -150,9 +157,9 @@ func main() { cmdScript.Flag("credentials-file", "The file that will cache the credentials retrieved from AWS. When not specified, will use the default AWS credentials file location. (env: SAML2AWS_CREDENTIALS_FILE)").Envar("SAML2AWS_CREDENTIALS_FILE").StringVar(&commonFlags.CredentialsFile) var shell string cmdScript. - Flag("shell", "Type of shell environment. Options include: bash, powershell, fish, env"). + Flag("shell", "Type of shell environment. Options include: bash, /bin/sh, powershell, fish, env"). Default("bash"). - EnumVar(&shell, "bash", "powershell", "fish", "env") + EnumVar(&shell, "bash", "/bin/sh", "powershell", "fish", "env") // Trigger the parsing of the command line inputs via kingpin command := kingpin.MustParse(app.Parse(os.Args[1:])) @@ -170,8 +177,8 @@ func main() { } if *quiet { - log.SetOutput(ioutil.Discard) - logrus.SetOutput(ioutil.Discard) + log.SetOutput(io.Discard) + logrus.SetOutput(io.Discard) } // Set the default transport settings so all http clients will pick them up. diff --git a/coverage.xml b/coverage.xml deleted file mode 100644 index 3f52527f0..000000000 --- a/coverage.xml +++ /dev/null @@ -1,11135 +0,0 @@ - - - - - /home/markw/Code/notgopath/saml2aws - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/go.mod b/go.mod index 169c09ce6..b7048eb4a 100644 --- a/go.mod +++ b/go.mod @@ -1,42 +1,46 @@ module github.com/versent/saml2aws/v2 -go 1.17 +go 1.20 require ( - github.com/99designs/keyring v1.1.6 - github.com/AlecAivazis/survey/v2 v2.3.2 + github.com/99designs/keyring v1.2.2 + github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e - github.com/PuerkitoBio/goquery v1.8.0 + github.com/PuerkitoBio/goquery v1.8.1 github.com/alecthomas/kingpin v2.2.6+incompatible github.com/avast/retry-go v3.0.0+incompatible - github.com/aws/aws-sdk-go v1.42.44 - github.com/beevik/etree v1.1.0 - github.com/danieljoos/wincred v1.1.2 - github.com/google/uuid v1.3.0 + github.com/aws/aws-sdk-go v1.47.10 + github.com/beevik/etree v1.2.0 + github.com/danieljoos/wincred v1.2.0 + github.com/google/uuid v1.4.0 + github.com/h2non/gock v1.2.0 github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621 github.com/marshallbrekka/go-u2fhost v0.0.0-20210111072507-3ccdec8c8105 github.com/mitchellh/go-homedir v1.1.0 - github.com/mxschmitt/playwright-go v0.1400.0 github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.8.1 + github.com/playwright-community/playwright-go v0.3900.1 + github.com/sirupsen/logrus v1.9.3 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 - github.com/stretchr/testify v1.7.0 - github.com/tidwall/gjson v1.13.0 - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd - gopkg.in/ini.v1 v1.66.3 + github.com/stretchr/testify v1.8.4 + github.com/tidwall/gjson v1.17.0 + golang.org/x/net v0.18.0 + gopkg.in/ini.v1 v1.67.0 ) require ( + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect github.com/andybalholm/cascadia v1.3.1 // indirect github.com/bearsh/hid v1.3.0 // indirect github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect + github.com/dvsekhvalnov/jose2go v1.5.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.0 // indirect + github.com/go-stack/stack v1.8.1 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect - github.com/gorilla/websocket v1.4.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/mattn/go-colorable v0.1.2 // indirect @@ -44,13 +48,13 @@ require ( github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect - golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect - golang.org/x/text v0.3.7 // indirect - gopkg.in/square/go-jose.v2 v2.6.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + github.com/tidwall/pretty v1.2.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/term v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index be8e98b4b..35a454fad 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/99designs/keyring v1.1.6 h1:kVDC2uCgVwecxCk+9zoCt2uEL6dt+dfVzMvGgnVcIuM= -github.com/99designs/keyring v1.1.6/go.mod h1:16e0ds7LGQQcT59QqkTg72Hh5ShM51Byv5PEmW6uoRU= -github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8= -github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= +github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= +github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= +github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= -github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= -github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= +github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= +github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= @@ -22,12 +24,12 @@ github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEq github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= -github.com/aws/aws-sdk-go v1.42.44 h1:vPlF4cUsdN5ETfvb7ewZFbFZyB6Rsfndt3kS2XqLXKo= -github.com/aws/aws-sdk-go v1.42.44/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= +github.com/aws/aws-sdk-go v1.47.10 h1:cvufN7WkD1nlOgpRopsmxKQlFp5X1MfyAw4r7BBORQc= +github.com/aws/aws-sdk-go v1.47.10/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/bearsh/hid v1.3.0 h1:GLNa8hvEzJxzQEEpheDUr2SivvH7iwTrJrDhFKutfX8= github.com/bearsh/hid v1.3.0/go.mod h1:KbQByg8WfPr92v7aaKAHTtZUEVG7e2XRpcF8+TopQv8= -github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= -github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/beevik/etree v1.2.0 h1:l7WETslUG/T+xOPs47dtd6jov2Ii/8/OjCldk5fYfQw= +github.com/beevik/etree v1.2.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -38,9 +40,10 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= -github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= -github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= +github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -48,14 +51,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b h1:HBah4D48ypg3J7Np4N+HY/ZR76fx3HEUGxDU6Uk39oQ= -github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= +github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= +github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -67,20 +74,22 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= -github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= +github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= -github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -90,20 +99,16 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621 h1:aMQ7pA4f06yOVXSulygyGvy4xA94fyzjUGs0iqQdMOI= github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621/go.mod h1:enrU/ug069Om7vWxuFE6nikLI2BZNwevMiGSo43Kt5w= -github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= -github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -122,13 +127,16 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxschmitt/playwright-go v0.1400.0 h1:HL8dbxcVEobE+pNjASeYGJJRmd4+9gyu/51XO7d3qF0= -github.com/mxschmitt/playwright-go v0.1400.0/go.mod h1:kUvZFgMneRGknVLtC2DKQ42lhZiCmWzxgBdGwjC0vkw= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/playwright-community/playwright-go v0.3900.1 h1:8BkmDxVzLTp3USQ50EyXJSXcz0XDMwNP5y29lHIZ9Fc= +github.com/playwright-community/playwright-go v0.3900.1/go.mod h1:mbNzMqt04IVRdhVfXWqmCxd81gCdL3BA5hj6/pVAIqM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -141,12 +149,13 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -159,50 +168,62 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M= -github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -211,46 +232,55 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.66.3 h1:jRskFVxYaMGAMUbN0UZ7niA9gzL9B49DOqE78vg0k3w= -gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/helper/linuxkeyring/linuxkeyring_linux.go b/helper/linuxkeyring/linuxkeyring_linux.go index 719b2e2be..123e7c787 100644 --- a/helper/linuxkeyring/linuxkeyring_linux.go +++ b/helper/linuxkeyring/linuxkeyring_linux.go @@ -14,8 +14,12 @@ type KeyringHelper struct { keyring keyring.Keyring } -func NewKeyringHelper() (*KeyringHelper, error) { - kr, err := keyring.Open(keyring.Config{ +type Configuration struct { + Backend string +} + +func NewKeyringHelper(config Configuration) (*KeyringHelper, error) { + c := keyring.Config{ AllowedBackends: []keyring.BackendType{ keyring.KWalletBackend, keyring.SecretServiceBackend, @@ -23,7 +27,14 @@ func NewKeyringHelper() (*KeyringHelper, error) { }, LibSecretCollectionName: "login", PassPrefix: "saml2aws", - }) + } + + // set the only allowed backend to be backend configured + if config.Backend != "" { + c.AllowedBackends = []keyring.BackendType{keyring.BackendType(config.Backend)} + } + + kr, err := keyring.Open(c) if err != nil { return nil, err diff --git a/helper/osxkeychain/keychain.go b/helper/osxkeychain/keychain.go index 3a9ec5ba7..555655f02 100644 --- a/helper/osxkeychain/keychain.go +++ b/helper/osxkeychain/keychain.go @@ -1,3 +1,4 @@ +//go:build darwin && cgo // +build darwin,cgo package osxkeychain diff --git a/helper/osxkeychain/osxkeychain.go b/helper/osxkeychain/osxkeychain.go index cc0f951b0..93e2bcdf3 100644 --- a/helper/osxkeychain/osxkeychain.go +++ b/helper/osxkeychain/osxkeychain.go @@ -1,3 +1,4 @@ +//go:build darwin && cgo // +build darwin,cgo package osxkeychain diff --git a/helper/osxkeychain/osxkeychain_test.go b/helper/osxkeychain/osxkeychain_test.go index de9cda899..78b85337c 100644 --- a/helper/osxkeychain/osxkeychain_test.go +++ b/helper/osxkeychain/osxkeychain_test.go @@ -1,3 +1,4 @@ +//go:build darwin && cgo // +build darwin,cgo // Copyright (c) 2016 David Calavera diff --git a/mocks/Page.go b/mocks/Page.go new file mode 100644 index 000000000..0a2b7bdd5 --- /dev/null +++ b/mocks/Page.go @@ -0,0 +1,2484 @@ +// Code generated by mockery v2.36.0. DO NOT EDIT. + +package mocks + +import ( + playwright "github.com/playwright-community/playwright-go" + mock "github.com/stretchr/testify/mock" +) + +// Page is an autogenerated mock type for the Page type +type Page struct { + mock.Mock +} + +// AddInitScript provides a mock function with given fields: script +func (_m *Page) AddInitScript(script playwright.Script) error { + ret := _m.Called(script) + + var r0 error + if rf, ok := ret.Get(0).(func(playwright.Script) error); ok { + r0 = rf(script) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AddScriptTag provides a mock function with given fields: options +func (_m *Page) AddScriptTag(options playwright.PageAddScriptTagOptions) (playwright.ElementHandle, error) { + ret := _m.Called(options) + + var r0 playwright.ElementHandle + var r1 error + if rf, ok := ret.Get(0).(func(playwright.PageAddScriptTagOptions) (playwright.ElementHandle, error)); ok { + return rf(options) + } + if rf, ok := ret.Get(0).(func(playwright.PageAddScriptTagOptions) playwright.ElementHandle); ok { + r0 = rf(options) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.ElementHandle) + } + } + + if rf, ok := ret.Get(1).(func(playwright.PageAddScriptTagOptions) error); ok { + r1 = rf(options) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AddStyleTag provides a mock function with given fields: options +func (_m *Page) AddStyleTag(options playwright.PageAddStyleTagOptions) (playwright.ElementHandle, error) { + ret := _m.Called(options) + + var r0 playwright.ElementHandle + var r1 error + if rf, ok := ret.Get(0).(func(playwright.PageAddStyleTagOptions) (playwright.ElementHandle, error)); ok { + return rf(options) + } + if rf, ok := ret.Get(0).(func(playwright.PageAddStyleTagOptions) playwright.ElementHandle); ok { + r0 = rf(options) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.ElementHandle) + } + } + + if rf, ok := ret.Get(1).(func(playwright.PageAddStyleTagOptions) error); ok { + r1 = rf(options) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BringToFront provides a mock function with given fields: +func (_m *Page) BringToFront() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Check provides a mock function with given fields: selector, options +func (_m *Page) Check(selector string, options ...playwright.PageCheckOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageCheckOptions) error); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Click provides a mock function with given fields: selector, options +func (_m *Page) Click(selector string, options ...playwright.PageClickOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageClickOptions) error); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Close provides a mock function with given fields: options +func (_m *Page) Close(options ...playwright.PageCloseOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(...playwright.PageCloseOptions) error); ok { + r0 = rf(options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Content provides a mock function with given fields: +func (_m *Page) Content() (string, error) { + ret := _m.Called() + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func() (string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Context provides a mock function with given fields: +func (_m *Page) Context() playwright.BrowserContext { + ret := _m.Called() + + var r0 playwright.BrowserContext + if rf, ok := ret.Get(0).(func() playwright.BrowserContext); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.BrowserContext) + } + } + + return r0 +} + +// Dblclick provides a mock function with given fields: selector, options +func (_m *Page) Dblclick(selector string, options ...playwright.PageDblclickOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageDblclickOptions) error); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DispatchEvent provides a mock function with given fields: selector, typ, eventInit, options +func (_m *Page) DispatchEvent(selector string, typ string, eventInit interface{}, options ...playwright.PageDispatchEventOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector, typ, eventInit) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, interface{}, ...playwright.PageDispatchEventOptions) error); ok { + r0 = rf(selector, typ, eventInit, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DragAndDrop provides a mock function with given fields: source, target, options +func (_m *Page) DragAndDrop(source string, target string, options ...playwright.PageDragAndDropOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, source, target) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, ...playwright.PageDragAndDropOptions) error); ok { + r0 = rf(source, target, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Emit provides a mock function with given fields: name, payload +func (_m *Page) Emit(name string, payload ...interface{}) bool { + var _ca []interface{} + _ca = append(_ca, name) + _ca = append(_ca, payload...) + ret := _m.Called(_ca...) + + var r0 bool + if rf, ok := ret.Get(0).(func(string, ...interface{}) bool); ok { + r0 = rf(name, payload...) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// EmulateMedia provides a mock function with given fields: options +func (_m *Page) EmulateMedia(options ...playwright.PageEmulateMediaOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(...playwright.PageEmulateMediaOptions) error); ok { + r0 = rf(options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// EvalOnSelector provides a mock function with given fields: selector, expression, arg, options +func (_m *Page) EvalOnSelector(selector string, expression string, arg interface{}, options ...playwright.PageEvalOnSelectorOptions) (interface{}, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector, expression, arg) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 interface{} + var r1 error + if rf, ok := ret.Get(0).(func(string, string, interface{}, ...playwright.PageEvalOnSelectorOptions) (interface{}, error)); ok { + return rf(selector, expression, arg, options...) + } + if rf, ok := ret.Get(0).(func(string, string, interface{}, ...playwright.PageEvalOnSelectorOptions) interface{}); ok { + r0 = rf(selector, expression, arg, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + if rf, ok := ret.Get(1).(func(string, string, interface{}, ...playwright.PageEvalOnSelectorOptions) error); ok { + r1 = rf(selector, expression, arg, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EvalOnSelectorAll provides a mock function with given fields: selector, expression, arg +func (_m *Page) EvalOnSelectorAll(selector string, expression string, arg ...interface{}) (interface{}, error) { + var _ca []interface{} + _ca = append(_ca, selector, expression) + _ca = append(_ca, arg...) + ret := _m.Called(_ca...) + + var r0 interface{} + var r1 error + if rf, ok := ret.Get(0).(func(string, string, ...interface{}) (interface{}, error)); ok { + return rf(selector, expression, arg...) + } + if rf, ok := ret.Get(0).(func(string, string, ...interface{}) interface{}); ok { + r0 = rf(selector, expression, arg...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + if rf, ok := ret.Get(1).(func(string, string, ...interface{}) error); ok { + r1 = rf(selector, expression, arg...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Evaluate provides a mock function with given fields: expression, arg +func (_m *Page) Evaluate(expression string, arg ...interface{}) (interface{}, error) { + var _ca []interface{} + _ca = append(_ca, expression) + _ca = append(_ca, arg...) + ret := _m.Called(_ca...) + + var r0 interface{} + var r1 error + if rf, ok := ret.Get(0).(func(string, ...interface{}) (interface{}, error)); ok { + return rf(expression, arg...) + } + if rf, ok := ret.Get(0).(func(string, ...interface{}) interface{}); ok { + r0 = rf(expression, arg...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + if rf, ok := ret.Get(1).(func(string, ...interface{}) error); ok { + r1 = rf(expression, arg...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EvaluateHandle provides a mock function with given fields: expression, arg +func (_m *Page) EvaluateHandle(expression string, arg ...interface{}) (playwright.JSHandle, error) { + var _ca []interface{} + _ca = append(_ca, expression) + _ca = append(_ca, arg...) + ret := _m.Called(_ca...) + + var r0 playwright.JSHandle + var r1 error + if rf, ok := ret.Get(0).(func(string, ...interface{}) (playwright.JSHandle, error)); ok { + return rf(expression, arg...) + } + if rf, ok := ret.Get(0).(func(string, ...interface{}) playwright.JSHandle); ok { + r0 = rf(expression, arg...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.JSHandle) + } + } + + if rf, ok := ret.Get(1).(func(string, ...interface{}) error); ok { + r1 = rf(expression, arg...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExpectConsoleMessage provides a mock function with given fields: cb, options +func (_m *Page) ExpectConsoleMessage(cb func() error, options ...playwright.PageExpectConsoleMessageOptions) (playwright.ConsoleMessage, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, cb) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.ConsoleMessage + var r1 error + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectConsoleMessageOptions) (playwright.ConsoleMessage, error)); ok { + return rf(cb, options...) + } + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectConsoleMessageOptions) playwright.ConsoleMessage); ok { + r0 = rf(cb, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.ConsoleMessage) + } + } + + if rf, ok := ret.Get(1).(func(func() error, ...playwright.PageExpectConsoleMessageOptions) error); ok { + r1 = rf(cb, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExpectDownload provides a mock function with given fields: cb, options +func (_m *Page) ExpectDownload(cb func() error, options ...playwright.PageExpectDownloadOptions) (playwright.Download, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, cb) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Download + var r1 error + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectDownloadOptions) (playwright.Download, error)); ok { + return rf(cb, options...) + } + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectDownloadOptions) playwright.Download); ok { + r0 = rf(cb, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Download) + } + } + + if rf, ok := ret.Get(1).(func(func() error, ...playwright.PageExpectDownloadOptions) error); ok { + r1 = rf(cb, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExpectEvent provides a mock function with given fields: event, cb, options +func (_m *Page) ExpectEvent(event string, cb func() error, options ...playwright.PageExpectEventOptions) (interface{}, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, event, cb) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 interface{} + var r1 error + if rf, ok := ret.Get(0).(func(string, func() error, ...playwright.PageExpectEventOptions) (interface{}, error)); ok { + return rf(event, cb, options...) + } + if rf, ok := ret.Get(0).(func(string, func() error, ...playwright.PageExpectEventOptions) interface{}); ok { + r0 = rf(event, cb, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + if rf, ok := ret.Get(1).(func(string, func() error, ...playwright.PageExpectEventOptions) error); ok { + r1 = rf(event, cb, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExpectFileChooser provides a mock function with given fields: cb, options +func (_m *Page) ExpectFileChooser(cb func() error, options ...playwright.PageExpectFileChooserOptions) (playwright.FileChooser, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, cb) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.FileChooser + var r1 error + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectFileChooserOptions) (playwright.FileChooser, error)); ok { + return rf(cb, options...) + } + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectFileChooserOptions) playwright.FileChooser); ok { + r0 = rf(cb, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.FileChooser) + } + } + + if rf, ok := ret.Get(1).(func(func() error, ...playwright.PageExpectFileChooserOptions) error); ok { + r1 = rf(cb, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExpectNavigation provides a mock function with given fields: cb, options +func (_m *Page) ExpectNavigation(cb func() error, options ...playwright.PageExpectNavigationOptions) (playwright.Response, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, cb) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Response + var r1 error + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectNavigationOptions) (playwright.Response, error)); ok { + return rf(cb, options...) + } + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectNavigationOptions) playwright.Response); ok { + r0 = rf(cb, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Response) + } + } + + if rf, ok := ret.Get(1).(func(func() error, ...playwright.PageExpectNavigationOptions) error); ok { + r1 = rf(cb, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExpectPopup provides a mock function with given fields: cb, options +func (_m *Page) ExpectPopup(cb func() error, options ...playwright.PageExpectPopupOptions) (playwright.Page, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, cb) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Page + var r1 error + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectPopupOptions) (playwright.Page, error)); ok { + return rf(cb, options...) + } + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectPopupOptions) playwright.Page); ok { + r0 = rf(cb, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Page) + } + } + + if rf, ok := ret.Get(1).(func(func() error, ...playwright.PageExpectPopupOptions) error); ok { + r1 = rf(cb, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExpectRequest provides a mock function with given fields: urlOrPredicate, cb, options +func (_m *Page) ExpectRequest(urlOrPredicate interface{}, cb func() error, options ...playwright.PageExpectRequestOptions) (playwright.Request, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, urlOrPredicate, cb) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Request + var r1 error + if rf, ok := ret.Get(0).(func(interface{}, func() error, ...playwright.PageExpectRequestOptions) (playwright.Request, error)); ok { + return rf(urlOrPredicate, cb, options...) + } + if rf, ok := ret.Get(0).(func(interface{}, func() error, ...playwright.PageExpectRequestOptions) playwright.Request); ok { + r0 = rf(urlOrPredicate, cb, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Request) + } + } + + if rf, ok := ret.Get(1).(func(interface{}, func() error, ...playwright.PageExpectRequestOptions) error); ok { + r1 = rf(urlOrPredicate, cb, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExpectRequestFinished provides a mock function with given fields: cb, options +func (_m *Page) ExpectRequestFinished(cb func() error, options ...playwright.PageExpectRequestFinishedOptions) (playwright.Request, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, cb) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Request + var r1 error + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectRequestFinishedOptions) (playwright.Request, error)); ok { + return rf(cb, options...) + } + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectRequestFinishedOptions) playwright.Request); ok { + r0 = rf(cb, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Request) + } + } + + if rf, ok := ret.Get(1).(func(func() error, ...playwright.PageExpectRequestFinishedOptions) error); ok { + r1 = rf(cb, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExpectResponse provides a mock function with given fields: urlOrPredicate, cb, options +func (_m *Page) ExpectResponse(urlOrPredicate interface{}, cb func() error, options ...playwright.PageExpectResponseOptions) (playwright.Response, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, urlOrPredicate, cb) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Response + var r1 error + if rf, ok := ret.Get(0).(func(interface{}, func() error, ...playwright.PageExpectResponseOptions) (playwright.Response, error)); ok { + return rf(urlOrPredicate, cb, options...) + } + if rf, ok := ret.Get(0).(func(interface{}, func() error, ...playwright.PageExpectResponseOptions) playwright.Response); ok { + r0 = rf(urlOrPredicate, cb, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Response) + } + } + + if rf, ok := ret.Get(1).(func(interface{}, func() error, ...playwright.PageExpectResponseOptions) error); ok { + r1 = rf(urlOrPredicate, cb, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExpectWebSocket provides a mock function with given fields: cb, options +func (_m *Page) ExpectWebSocket(cb func() error, options ...playwright.PageExpectWebSocketOptions) (playwright.WebSocket, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, cb) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.WebSocket + var r1 error + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectWebSocketOptions) (playwright.WebSocket, error)); ok { + return rf(cb, options...) + } + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectWebSocketOptions) playwright.WebSocket); ok { + r0 = rf(cb, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.WebSocket) + } + } + + if rf, ok := ret.Get(1).(func(func() error, ...playwright.PageExpectWebSocketOptions) error); ok { + r1 = rf(cb, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExpectWorker provides a mock function with given fields: cb, options +func (_m *Page) ExpectWorker(cb func() error, options ...playwright.PageExpectWorkerOptions) (playwright.Worker, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, cb) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Worker + var r1 error + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectWorkerOptions) (playwright.Worker, error)); ok { + return rf(cb, options...) + } + if rf, ok := ret.Get(0).(func(func() error, ...playwright.PageExpectWorkerOptions) playwright.Worker); ok { + r0 = rf(cb, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Worker) + } + } + + if rf, ok := ret.Get(1).(func(func() error, ...playwright.PageExpectWorkerOptions) error); ok { + r1 = rf(cb, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExposeBinding provides a mock function with given fields: name, binding, handle +func (_m *Page) ExposeBinding(name string, binding playwright.BindingCallFunction, handle ...bool) error { + _va := make([]interface{}, len(handle)) + for _i := range handle { + _va[_i] = handle[_i] + } + var _ca []interface{} + _ca = append(_ca, name, binding) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, playwright.BindingCallFunction, ...bool) error); ok { + r0 = rf(name, binding, handle...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ExposeFunction provides a mock function with given fields: name, binding +func (_m *Page) ExposeFunction(name string, binding func([]interface{}) interface{}) error { + ret := _m.Called(name, binding) + + var r0 error + if rf, ok := ret.Get(0).(func(string, func([]interface{}) interface{}) error); ok { + r0 = rf(name, binding) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Fill provides a mock function with given fields: selector, value, options +func (_m *Page) Fill(selector string, value string, options ...playwright.PageFillOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector, value) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, ...playwright.PageFillOptions) error); ok { + r0 = rf(selector, value, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Focus provides a mock function with given fields: selector, options +func (_m *Page) Focus(selector string, options ...playwright.PageFocusOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageFocusOptions) error); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Frame provides a mock function with given fields: options +func (_m *Page) Frame(options ...playwright.PageFrameOptions) playwright.Frame { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Frame + if rf, ok := ret.Get(0).(func(...playwright.PageFrameOptions) playwright.Frame); ok { + r0 = rf(options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Frame) + } + } + + return r0 +} + +// FrameLocator provides a mock function with given fields: selector +func (_m *Page) FrameLocator(selector string) playwright.FrameLocator { + ret := _m.Called(selector) + + var r0 playwright.FrameLocator + if rf, ok := ret.Get(0).(func(string) playwright.FrameLocator); ok { + r0 = rf(selector) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.FrameLocator) + } + } + + return r0 +} + +// Frames provides a mock function with given fields: +func (_m *Page) Frames() []playwright.Frame { + ret := _m.Called() + + var r0 []playwright.Frame + if rf, ok := ret.Get(0).(func() []playwright.Frame); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]playwright.Frame) + } + } + + return r0 +} + +// GetAttribute provides a mock function with given fields: selector, name, options +func (_m *Page) GetAttribute(selector string, name string, options ...playwright.PageGetAttributeOptions) (string, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector, name) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string, string, ...playwright.PageGetAttributeOptions) (string, error)); ok { + return rf(selector, name, options...) + } + if rf, ok := ret.Get(0).(func(string, string, ...playwright.PageGetAttributeOptions) string); ok { + r0 = rf(selector, name, options...) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string, string, ...playwright.PageGetAttributeOptions) error); ok { + r1 = rf(selector, name, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetByAltText provides a mock function with given fields: text, options +func (_m *Page) GetByAltText(text interface{}, options ...playwright.PageGetByAltTextOptions) playwright.Locator { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, text) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Locator + if rf, ok := ret.Get(0).(func(interface{}, ...playwright.PageGetByAltTextOptions) playwright.Locator); ok { + r0 = rf(text, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Locator) + } + } + + return r0 +} + +// GetByLabel provides a mock function with given fields: text, options +func (_m *Page) GetByLabel(text interface{}, options ...playwright.PageGetByLabelOptions) playwright.Locator { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, text) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Locator + if rf, ok := ret.Get(0).(func(interface{}, ...playwright.PageGetByLabelOptions) playwright.Locator); ok { + r0 = rf(text, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Locator) + } + } + + return r0 +} + +// GetByPlaceholder provides a mock function with given fields: text, options +func (_m *Page) GetByPlaceholder(text interface{}, options ...playwright.PageGetByPlaceholderOptions) playwright.Locator { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, text) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Locator + if rf, ok := ret.Get(0).(func(interface{}, ...playwright.PageGetByPlaceholderOptions) playwright.Locator); ok { + r0 = rf(text, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Locator) + } + } + + return r0 +} + +// GetByRole provides a mock function with given fields: role, options +func (_m *Page) GetByRole(role playwright.AriaRole, options ...playwright.PageGetByRoleOptions) playwright.Locator { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, role) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Locator + if rf, ok := ret.Get(0).(func(playwright.AriaRole, ...playwright.PageGetByRoleOptions) playwright.Locator); ok { + r0 = rf(role, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Locator) + } + } + + return r0 +} + +// GetByTestId provides a mock function with given fields: testId +func (_m *Page) GetByTestId(testId interface{}) playwright.Locator { + ret := _m.Called(testId) + + var r0 playwright.Locator + if rf, ok := ret.Get(0).(func(interface{}) playwright.Locator); ok { + r0 = rf(testId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Locator) + } + } + + return r0 +} + +// GetByText provides a mock function with given fields: text, options +func (_m *Page) GetByText(text interface{}, options ...playwright.PageGetByTextOptions) playwright.Locator { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, text) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Locator + if rf, ok := ret.Get(0).(func(interface{}, ...playwright.PageGetByTextOptions) playwright.Locator); ok { + r0 = rf(text, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Locator) + } + } + + return r0 +} + +// GetByTitle provides a mock function with given fields: text, options +func (_m *Page) GetByTitle(text interface{}, options ...playwright.PageGetByTitleOptions) playwright.Locator { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, text) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Locator + if rf, ok := ret.Get(0).(func(interface{}, ...playwright.PageGetByTitleOptions) playwright.Locator); ok { + r0 = rf(text, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Locator) + } + } + + return r0 +} + +// GoBack provides a mock function with given fields: options +func (_m *Page) GoBack(options ...playwright.PageGoBackOptions) (playwright.Response, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Response + var r1 error + if rf, ok := ret.Get(0).(func(...playwright.PageGoBackOptions) (playwright.Response, error)); ok { + return rf(options...) + } + if rf, ok := ret.Get(0).(func(...playwright.PageGoBackOptions) playwright.Response); ok { + r0 = rf(options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Response) + } + } + + if rf, ok := ret.Get(1).(func(...playwright.PageGoBackOptions) error); ok { + r1 = rf(options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GoForward provides a mock function with given fields: options +func (_m *Page) GoForward(options ...playwright.PageGoForwardOptions) (playwright.Response, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Response + var r1 error + if rf, ok := ret.Get(0).(func(...playwright.PageGoForwardOptions) (playwright.Response, error)); ok { + return rf(options...) + } + if rf, ok := ret.Get(0).(func(...playwright.PageGoForwardOptions) playwright.Response); ok { + r0 = rf(options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Response) + } + } + + if rf, ok := ret.Get(1).(func(...playwright.PageGoForwardOptions) error); ok { + r1 = rf(options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Goto provides a mock function with given fields: url, options +func (_m *Page) Goto(url string, options ...playwright.PageGotoOptions) (playwright.Response, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, url) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Response + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageGotoOptions) (playwright.Response, error)); ok { + return rf(url, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageGotoOptions) playwright.Response); ok { + r0 = rf(url, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Response) + } + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageGotoOptions) error); ok { + r1 = rf(url, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Hover provides a mock function with given fields: selector, options +func (_m *Page) Hover(selector string, options ...playwright.PageHoverOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageHoverOptions) error); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InnerHTML provides a mock function with given fields: selector, options +func (_m *Page) InnerHTML(selector string, options ...playwright.PageInnerHTMLOptions) (string, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageInnerHTMLOptions) (string, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageInnerHTMLOptions) string); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageInnerHTMLOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InnerText provides a mock function with given fields: selector, options +func (_m *Page) InnerText(selector string, options ...playwright.PageInnerTextOptions) (string, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageInnerTextOptions) (string, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageInnerTextOptions) string); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageInnerTextOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InputValue provides a mock function with given fields: selector, options +func (_m *Page) InputValue(selector string, options ...playwright.PageInputValueOptions) (string, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageInputValueOptions) (string, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageInputValueOptions) string); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageInputValueOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsChecked provides a mock function with given fields: selector, options +func (_m *Page) IsChecked(selector string, options ...playwright.PageIsCheckedOptions) (bool, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsCheckedOptions) (bool, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsCheckedOptions) bool); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageIsCheckedOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsClosed provides a mock function with given fields: +func (_m *Page) IsClosed() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// IsDisabled provides a mock function with given fields: selector, options +func (_m *Page) IsDisabled(selector string, options ...playwright.PageIsDisabledOptions) (bool, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsDisabledOptions) (bool, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsDisabledOptions) bool); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageIsDisabledOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsEditable provides a mock function with given fields: selector, options +func (_m *Page) IsEditable(selector string, options ...playwright.PageIsEditableOptions) (bool, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsEditableOptions) (bool, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsEditableOptions) bool); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageIsEditableOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsEnabled provides a mock function with given fields: selector, options +func (_m *Page) IsEnabled(selector string, options ...playwright.PageIsEnabledOptions) (bool, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsEnabledOptions) (bool, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsEnabledOptions) bool); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageIsEnabledOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsHidden provides a mock function with given fields: selector, options +func (_m *Page) IsHidden(selector string, options ...playwright.PageIsHiddenOptions) (bool, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsHiddenOptions) (bool, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsHiddenOptions) bool); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageIsHiddenOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsVisible provides a mock function with given fields: selector, options +func (_m *Page) IsVisible(selector string, options ...playwright.PageIsVisibleOptions) (bool, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsVisibleOptions) (bool, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageIsVisibleOptions) bool); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageIsVisibleOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Keyboard provides a mock function with given fields: +func (_m *Page) Keyboard() playwright.Keyboard { + ret := _m.Called() + + var r0 playwright.Keyboard + if rf, ok := ret.Get(0).(func() playwright.Keyboard); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Keyboard) + } + } + + return r0 +} + +// ListenerCount provides a mock function with given fields: name +func (_m *Page) ListenerCount(name string) int { + ret := _m.Called(name) + + var r0 int + if rf, ok := ret.Get(0).(func(string) int); ok { + r0 = rf(name) + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// Locator provides a mock function with given fields: selector, options +func (_m *Page) Locator(selector string, options ...playwright.PageLocatorOptions) playwright.Locator { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Locator + if rf, ok := ret.Get(0).(func(string, ...playwright.PageLocatorOptions) playwright.Locator); ok { + r0 = rf(selector, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Locator) + } + } + + return r0 +} + +// MainFrame provides a mock function with given fields: +func (_m *Page) MainFrame() playwright.Frame { + ret := _m.Called() + + var r0 playwright.Frame + if rf, ok := ret.Get(0).(func() playwright.Frame); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Frame) + } + } + + return r0 +} + +// Mouse provides a mock function with given fields: +func (_m *Page) Mouse() playwright.Mouse { + ret := _m.Called() + + var r0 playwright.Mouse + if rf, ok := ret.Get(0).(func() playwright.Mouse); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Mouse) + } + } + + return r0 +} + +// On provides a mock function with given fields: name, handler +func (_m *Page) On(name string, handler interface{}) { + _m.Called(name, handler) +} + +// OnClose provides a mock function with given fields: fn +func (_m *Page) OnClose(fn func(playwright.Page)) { + _m.Called(fn) +} + +// OnConsole provides a mock function with given fields: fn +func (_m *Page) OnConsole(fn func(playwright.ConsoleMessage)) { + _m.Called(fn) +} + +// OnCrash provides a mock function with given fields: fn +func (_m *Page) OnCrash(fn func(playwright.Page)) { + _m.Called(fn) +} + +// OnDOMContentLoaded provides a mock function with given fields: fn +func (_m *Page) OnDOMContentLoaded(fn func(playwright.Page)) { + _m.Called(fn) +} + +// OnDialog provides a mock function with given fields: fn +func (_m *Page) OnDialog(fn func(playwright.Dialog)) { + _m.Called(fn) +} + +// OnDownload provides a mock function with given fields: fn +func (_m *Page) OnDownload(fn func(playwright.Download)) { + _m.Called(fn) +} + +// OnFileChooser provides a mock function with given fields: fn +func (_m *Page) OnFileChooser(fn func(playwright.FileChooser)) { + _m.Called(fn) +} + +// OnFrameAttached provides a mock function with given fields: fn +func (_m *Page) OnFrameAttached(fn func(playwright.Frame)) { + _m.Called(fn) +} + +// OnFrameDetached provides a mock function with given fields: fn +func (_m *Page) OnFrameDetached(fn func(playwright.Frame)) { + _m.Called(fn) +} + +// OnFrameNavigated provides a mock function with given fields: fn +func (_m *Page) OnFrameNavigated(fn func(playwright.Frame)) { + _m.Called(fn) +} + +// OnLoad provides a mock function with given fields: fn +func (_m *Page) OnLoad(fn func(playwright.Page)) { + _m.Called(fn) +} + +// OnPageError provides a mock function with given fields: fn +func (_m *Page) OnPageError(fn func(*playwright.Error)) { + _m.Called(fn) +} + +// OnPopup provides a mock function with given fields: fn +func (_m *Page) OnPopup(fn func(playwright.Page)) { + _m.Called(fn) +} + +// OnRequest provides a mock function with given fields: fn +func (_m *Page) OnRequest(fn func(playwright.Request)) { + _m.Called(fn) +} + +// OnRequestFailed provides a mock function with given fields: fn +func (_m *Page) OnRequestFailed(fn func(playwright.Request)) { + _m.Called(fn) +} + +// OnRequestFinished provides a mock function with given fields: fn +func (_m *Page) OnRequestFinished(fn func(playwright.Request)) { + _m.Called(fn) +} + +// OnResponse provides a mock function with given fields: fn +func (_m *Page) OnResponse(fn func(playwright.Response)) { + _m.Called(fn) +} + +// OnWebSocket provides a mock function with given fields: fn +func (_m *Page) OnWebSocket(fn func(playwright.WebSocket)) { + _m.Called(fn) +} + +// OnWorker provides a mock function with given fields: fn +func (_m *Page) OnWorker(fn func(playwright.Worker)) { + _m.Called(fn) +} + +// Once provides a mock function with given fields: name, handler +func (_m *Page) Once(name string, handler interface{}) { + _m.Called(name, handler) +} + +// Opener provides a mock function with given fields: +func (_m *Page) Opener() (playwright.Page, error) { + ret := _m.Called() + + var r0 playwright.Page + var r1 error + if rf, ok := ret.Get(0).(func() (playwright.Page, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() playwright.Page); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Page) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PDF provides a mock function with given fields: options +func (_m *Page) PDF(options ...playwright.PagePdfOptions) ([]byte, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(...playwright.PagePdfOptions) ([]byte, error)); ok { + return rf(options...) + } + if rf, ok := ret.Get(0).(func(...playwright.PagePdfOptions) []byte); ok { + r0 = rf(options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(...playwright.PagePdfOptions) error); ok { + r1 = rf(options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Pause provides a mock function with given fields: +func (_m *Page) Pause() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Press provides a mock function with given fields: selector, key, options +func (_m *Page) Press(selector string, key string, options ...playwright.PagePressOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector, key) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, ...playwright.PagePressOptions) error); ok { + r0 = rf(selector, key, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// QuerySelector provides a mock function with given fields: selector, options +func (_m *Page) QuerySelector(selector string, options ...playwright.PageQuerySelectorOptions) (playwright.ElementHandle, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.ElementHandle + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageQuerySelectorOptions) (playwright.ElementHandle, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageQuerySelectorOptions) playwright.ElementHandle); ok { + r0 = rf(selector, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.ElementHandle) + } + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageQuerySelectorOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// QuerySelectorAll provides a mock function with given fields: selector +func (_m *Page) QuerySelectorAll(selector string) ([]playwright.ElementHandle, error) { + ret := _m.Called(selector) + + var r0 []playwright.ElementHandle + var r1 error + if rf, ok := ret.Get(0).(func(string) ([]playwright.ElementHandle, error)); ok { + return rf(selector) + } + if rf, ok := ret.Get(0).(func(string) []playwright.ElementHandle); ok { + r0 = rf(selector) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]playwright.ElementHandle) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(selector) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Reload provides a mock function with given fields: options +func (_m *Page) Reload(options ...playwright.PageReloadOptions) (playwright.Response, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.Response + var r1 error + if rf, ok := ret.Get(0).(func(...playwright.PageReloadOptions) (playwright.Response, error)); ok { + return rf(options...) + } + if rf, ok := ret.Get(0).(func(...playwright.PageReloadOptions) playwright.Response); ok { + r0 = rf(options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Response) + } + } + + if rf, ok := ret.Get(1).(func(...playwright.PageReloadOptions) error); ok { + r1 = rf(options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveListener provides a mock function with given fields: name, handler +func (_m *Page) RemoveListener(name string, handler interface{}) { + _m.Called(name, handler) +} + +// Request provides a mock function with given fields: +func (_m *Page) Request() playwright.APIRequestContext { + ret := _m.Called() + + var r0 playwright.APIRequestContext + if rf, ok := ret.Get(0).(func() playwright.APIRequestContext); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.APIRequestContext) + } + } + + return r0 +} + +// Route provides a mock function with given fields: url, handler, times +func (_m *Page) Route(url interface{}, handler func(playwright.Route), times ...int) error { + _va := make([]interface{}, len(times)) + for _i := range times { + _va[_i] = times[_i] + } + var _ca []interface{} + _ca = append(_ca, url, handler) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}, func(playwright.Route), ...int) error); ok { + r0 = rf(url, handler, times...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RouteFromHAR provides a mock function with given fields: har, options +func (_m *Page) RouteFromHAR(har string, options ...playwright.PageRouteFromHAROptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, har) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageRouteFromHAROptions) error); ok { + r0 = rf(har, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Screenshot provides a mock function with given fields: options +func (_m *Page) Screenshot(options ...playwright.PageScreenshotOptions) ([]byte, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(...playwright.PageScreenshotOptions) ([]byte, error)); ok { + return rf(options...) + } + if rf, ok := ret.Get(0).(func(...playwright.PageScreenshotOptions) []byte); ok { + r0 = rf(options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(...playwright.PageScreenshotOptions) error); ok { + r1 = rf(options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SelectOption provides a mock function with given fields: selector, values, options +func (_m *Page) SelectOption(selector string, values playwright.SelectOptionValues, options ...playwright.PageSelectOptionOptions) ([]string, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector, values) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(string, playwright.SelectOptionValues, ...playwright.PageSelectOptionOptions) ([]string, error)); ok { + return rf(selector, values, options...) + } + if rf, ok := ret.Get(0).(func(string, playwright.SelectOptionValues, ...playwright.PageSelectOptionOptions) []string); ok { + r0 = rf(selector, values, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(string, playwright.SelectOptionValues, ...playwright.PageSelectOptionOptions) error); ok { + r1 = rf(selector, values, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetChecked provides a mock function with given fields: selector, checked, options +func (_m *Page) SetChecked(selector string, checked bool, options ...playwright.PageSetCheckedOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector, checked) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, bool, ...playwright.PageSetCheckedOptions) error); ok { + r0 = rf(selector, checked, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetContent provides a mock function with given fields: html, options +func (_m *Page) SetContent(html string, options ...playwright.PageSetContentOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, html) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageSetContentOptions) error); ok { + r0 = rf(html, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetDefaultNavigationTimeout provides a mock function with given fields: timeout +func (_m *Page) SetDefaultNavigationTimeout(timeout float64) { + _m.Called(timeout) +} + +// SetDefaultTimeout provides a mock function with given fields: timeout +func (_m *Page) SetDefaultTimeout(timeout float64) { + _m.Called(timeout) +} + +// SetExtraHTTPHeaders provides a mock function with given fields: headers +func (_m *Page) SetExtraHTTPHeaders(headers map[string]string) error { + ret := _m.Called(headers) + + var r0 error + if rf, ok := ret.Get(0).(func(map[string]string) error); ok { + r0 = rf(headers) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetInputFiles provides a mock function with given fields: selector, files, options +func (_m *Page) SetInputFiles(selector string, files []playwright.InputFile, options ...playwright.PageSetInputFilesOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector, files) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, []playwright.InputFile, ...playwright.PageSetInputFilesOptions) error); ok { + r0 = rf(selector, files, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetViewportSize provides a mock function with given fields: width, height +func (_m *Page) SetViewportSize(width int, height int) error { + ret := _m.Called(width, height) + + var r0 error + if rf, ok := ret.Get(0).(func(int, int) error); ok { + r0 = rf(width, height) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Tap provides a mock function with given fields: selector, options +func (_m *Page) Tap(selector string, options ...playwright.PageTapOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageTapOptions) error); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TextContent provides a mock function with given fields: selector, options +func (_m *Page) TextContent(selector string, options ...playwright.PageTextContentOptions) (string, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageTextContentOptions) (string, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageTextContentOptions) string); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageTextContentOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Title provides a mock function with given fields: +func (_m *Page) Title() (string, error) { + ret := _m.Called() + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func() (string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Touchscreen provides a mock function with given fields: +func (_m *Page) Touchscreen() playwright.Touchscreen { + ret := _m.Called() + + var r0 playwright.Touchscreen + if rf, ok := ret.Get(0).(func() playwright.Touchscreen); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Touchscreen) + } + } + + return r0 +} + +// Type provides a mock function with given fields: selector, text, options +func (_m *Page) Type(selector string, text string, options ...playwright.PageTypeOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector, text) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, ...playwright.PageTypeOptions) error); ok { + r0 = rf(selector, text, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// URL provides a mock function with given fields: +func (_m *Page) URL() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Uncheck provides a mock function with given fields: selector, options +func (_m *Page) Uncheck(selector string, options ...playwright.PageUncheckOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageUncheckOptions) error); ok { + r0 = rf(selector, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Unroute provides a mock function with given fields: url, handler +func (_m *Page) Unroute(url interface{}, handler ...func(playwright.Route)) error { + _va := make([]interface{}, len(handler)) + for _i := range handler { + _va[_i] = handler[_i] + } + var _ca []interface{} + _ca = append(_ca, url) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}, ...func(playwright.Route)) error); ok { + r0 = rf(url, handler...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Video provides a mock function with given fields: +func (_m *Page) Video() playwright.Video { + ret := _m.Called() + + var r0 playwright.Video + if rf, ok := ret.Get(0).(func() playwright.Video); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Video) + } + } + + return r0 +} + +// ViewportSize provides a mock function with given fields: +func (_m *Page) ViewportSize() *playwright.Size { + ret := _m.Called() + + var r0 *playwright.Size + if rf, ok := ret.Get(0).(func() *playwright.Size); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*playwright.Size) + } + } + + return r0 +} + +// WaitForEvent provides a mock function with given fields: event, options +func (_m *Page) WaitForEvent(event string, options ...playwright.PageWaitForEventOptions) (interface{}, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, event) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 interface{} + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageWaitForEventOptions) (interface{}, error)); ok { + return rf(event, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageWaitForEventOptions) interface{}); ok { + r0 = rf(event, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageWaitForEventOptions) error); ok { + r1 = rf(event, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// WaitForFunction provides a mock function with given fields: expression, arg, options +func (_m *Page) WaitForFunction(expression string, arg interface{}, options ...playwright.PageWaitForFunctionOptions) (playwright.JSHandle, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, expression, arg) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.JSHandle + var r1 error + if rf, ok := ret.Get(0).(func(string, interface{}, ...playwright.PageWaitForFunctionOptions) (playwright.JSHandle, error)); ok { + return rf(expression, arg, options...) + } + if rf, ok := ret.Get(0).(func(string, interface{}, ...playwright.PageWaitForFunctionOptions) playwright.JSHandle); ok { + r0 = rf(expression, arg, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.JSHandle) + } + } + + if rf, ok := ret.Get(1).(func(string, interface{}, ...playwright.PageWaitForFunctionOptions) error); ok { + r1 = rf(expression, arg, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// WaitForLoadState provides a mock function with given fields: options +func (_m *Page) WaitForLoadState(options ...playwright.PageWaitForLoadStateOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(...playwright.PageWaitForLoadStateOptions) error); ok { + r0 = rf(options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// WaitForSelector provides a mock function with given fields: selector, options +func (_m *Page) WaitForSelector(selector string, options ...playwright.PageWaitForSelectorOptions) (playwright.ElementHandle, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, selector) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 playwright.ElementHandle + var r1 error + if rf, ok := ret.Get(0).(func(string, ...playwright.PageWaitForSelectorOptions) (playwright.ElementHandle, error)); ok { + return rf(selector, options...) + } + if rf, ok := ret.Get(0).(func(string, ...playwright.PageWaitForSelectorOptions) playwright.ElementHandle); ok { + r0 = rf(selector, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.ElementHandle) + } + } + + if rf, ok := ret.Get(1).(func(string, ...playwright.PageWaitForSelectorOptions) error); ok { + r1 = rf(selector, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// WaitForTimeout provides a mock function with given fields: timeout +func (_m *Page) WaitForTimeout(timeout float64) { + _m.Called(timeout) +} + +// WaitForURL provides a mock function with given fields: url, options +func (_m *Page) WaitForURL(url interface{}, options ...playwright.PageWaitForURLOptions) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, url) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}, ...playwright.PageWaitForURLOptions) error); ok { + r0 = rf(url, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Workers provides a mock function with given fields: +func (_m *Page) Workers() []playwright.Worker { + ret := _m.Called() + + var r0 []playwright.Worker + if rf, ok := ret.Get(0).(func() []playwright.Worker); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]playwright.Worker) + } + } + + return r0 +} + +// NewPage creates a new instance of Page. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewPage(t interface { + mock.TestingT + Cleanup(func()) +}) *Page { + mock := &Page{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/Prompter.go b/mocks/Prompter.go index e1453dd8d..ff4baf1a2 100644 --- a/mocks/Prompter.go +++ b/mocks/Prompter.go @@ -1,4 +1,4 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v2.36.0. DO NOT EDIT. package mocks @@ -28,13 +28,16 @@ func (_m *Prompter) ChooseWithDefault(_a0 string, _a1 string, _a2 []string) (str ret := _m.Called(_a0, _a1, _a2) var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string, string, []string) (string, error)); ok { + return rf(_a0, _a1, _a2) + } if rf, ok := ret.Get(0).(func(string, string, []string) string); ok { r0 = rf(_a0, _a1, _a2) } else { r0 = ret.Get(0).(string) } - var r1 error if rf, ok := ret.Get(1).(func(string, string, []string) error); ok { r1 = rf(_a0, _a1, _a2) } else { @@ -44,6 +47,11 @@ func (_m *Prompter) ChooseWithDefault(_a0 string, _a1 string, _a2 []string) (str return r0, r1 } +// Display provides a mock function with given fields: _a0 +func (_m *Prompter) Display(_a0 string) { + _m.Called(_a0) +} + // Password provides a mock function with given fields: _a0 func (_m *Prompter) Password(_a0 string) string { ret := _m.Called(_a0) @@ -99,3 +107,17 @@ func (_m *Prompter) StringRequired(_a0 string) string { return r0 } + +// NewPrompter creates a new instance of Prompter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewPrompter(t interface { + mock.TestingT + Cleanup(func()) +}) *Prompter { + mock := &Prompter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/Request.go b/mocks/Request.go new file mode 100644 index 000000000..69e312a6b --- /dev/null +++ b/mocks/Request.go @@ -0,0 +1,369 @@ +// Code generated by mockery v2.36.0. DO NOT EDIT. + +package mocks + +import ( + playwright "github.com/playwright-community/playwright-go" + mock "github.com/stretchr/testify/mock" +) + +// Request is an autogenerated mock type for the Request type +type Request struct { + mock.Mock +} + +// AllHeaders provides a mock function with given fields: +func (_m *Request) AllHeaders() (map[string]string, error) { + ret := _m.Called() + + var r0 map[string]string + var r1 error + if rf, ok := ret.Get(0).(func() (map[string]string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() map[string]string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]string) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Failure provides a mock function with given fields: +func (_m *Request) Failure() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Frame provides a mock function with given fields: +func (_m *Request) Frame() playwright.Frame { + ret := _m.Called() + + var r0 playwright.Frame + if rf, ok := ret.Get(0).(func() playwright.Frame); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Frame) + } + } + + return r0 +} + +// HeaderValue provides a mock function with given fields: name +func (_m *Request) HeaderValue(name string) (string, error) { + ret := _m.Called(name) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string) (string, error)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(name) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Headers provides a mock function with given fields: +func (_m *Request) Headers() map[string]string { + ret := _m.Called() + + var r0 map[string]string + if rf, ok := ret.Get(0).(func() map[string]string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]string) + } + } + + return r0 +} + +// HeadersArray provides a mock function with given fields: +func (_m *Request) HeadersArray() ([]playwright.NameValue, error) { + ret := _m.Called() + + var r0 []playwright.NameValue + var r1 error + if rf, ok := ret.Get(0).(func() ([]playwright.NameValue, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []playwright.NameValue); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]playwright.NameValue) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsNavigationRequest provides a mock function with given fields: +func (_m *Request) IsNavigationRequest() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Method provides a mock function with given fields: +func (_m *Request) Method() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// PostData provides a mock function with given fields: +func (_m *Request) PostData() (string, error) { + ret := _m.Called() + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func() (string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PostDataBuffer provides a mock function with given fields: +func (_m *Request) PostDataBuffer() ([]byte, error) { + ret := _m.Called() + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func() ([]byte, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []byte); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PostDataJSON provides a mock function with given fields: v +func (_m *Request) PostDataJSON(v interface{}) error { + ret := _m.Called(v) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(v) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RedirectedFrom provides a mock function with given fields: +func (_m *Request) RedirectedFrom() playwright.Request { + ret := _m.Called() + + var r0 playwright.Request + if rf, ok := ret.Get(0).(func() playwright.Request); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Request) + } + } + + return r0 +} + +// RedirectedTo provides a mock function with given fields: +func (_m *Request) RedirectedTo() playwright.Request { + ret := _m.Called() + + var r0 playwright.Request + if rf, ok := ret.Get(0).(func() playwright.Request); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Request) + } + } + + return r0 +} + +// ResourceType provides a mock function with given fields: +func (_m *Request) ResourceType() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Response provides a mock function with given fields: +func (_m *Request) Response() (playwright.Response, error) { + ret := _m.Called() + + var r0 playwright.Response + var r1 error + if rf, ok := ret.Get(0).(func() (playwright.Response, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() playwright.Response); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Response) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Sizes provides a mock function with given fields: +func (_m *Request) Sizes() (*playwright.RequestSizesResult, error) { + ret := _m.Called() + + var r0 *playwright.RequestSizesResult + var r1 error + if rf, ok := ret.Get(0).(func() (*playwright.RequestSizesResult, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() *playwright.RequestSizesResult); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*playwright.RequestSizesResult) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Timing provides a mock function with given fields: +func (_m *Request) Timing() *playwright.RequestTiming { + ret := _m.Called() + + var r0 *playwright.RequestTiming + if rf, ok := ret.Get(0).(func() *playwright.RequestTiming); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*playwright.RequestTiming) + } + } + + return r0 +} + +// URL provides a mock function with given fields: +func (_m *Request) URL() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// NewRequest creates a new instance of Request. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRequest(t interface { + mock.TestingT + Cleanup(func()) +}) *Request { + mock := &Request{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/Response.go b/mocks/Response.go new file mode 100644 index 000000000..583aa83d6 --- /dev/null +++ b/mocks/Response.go @@ -0,0 +1,377 @@ +// Code generated by mockery v2.36.0. DO NOT EDIT. + +package mocks + +import ( + playwright "github.com/playwright-community/playwright-go" + mock "github.com/stretchr/testify/mock" +) + +// Response is an autogenerated mock type for the Response type +type Response struct { + mock.Mock +} + +// AllHeaders provides a mock function with given fields: +func (_m *Response) AllHeaders() (map[string]string, error) { + ret := _m.Called() + + var r0 map[string]string + var r1 error + if rf, ok := ret.Get(0).(func() (map[string]string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() map[string]string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]string) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Body provides a mock function with given fields: +func (_m *Response) Body() ([]byte, error) { + ret := _m.Called() + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func() ([]byte, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []byte); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Finished provides a mock function with given fields: +func (_m *Response) Finished() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Frame provides a mock function with given fields: +func (_m *Response) Frame() playwright.Frame { + ret := _m.Called() + + var r0 playwright.Frame + if rf, ok := ret.Get(0).(func() playwright.Frame); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Frame) + } + } + + return r0 +} + +// FromServiceWorker provides a mock function with given fields: +func (_m *Response) FromServiceWorker() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// HeaderValue provides a mock function with given fields: name +func (_m *Response) HeaderValue(name string) (string, error) { + ret := _m.Called(name) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string) (string, error)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(name) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// HeaderValues provides a mock function with given fields: name +func (_m *Response) HeaderValues(name string) ([]string, error) { + ret := _m.Called(name) + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(string) ([]string, error)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) []string); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Headers provides a mock function with given fields: +func (_m *Response) Headers() map[string]string { + ret := _m.Called() + + var r0 map[string]string + if rf, ok := ret.Get(0).(func() map[string]string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]string) + } + } + + return r0 +} + +// HeadersArray provides a mock function with given fields: +func (_m *Response) HeadersArray() ([]playwright.NameValue, error) { + ret := _m.Called() + + var r0 []playwright.NameValue + var r1 error + if rf, ok := ret.Get(0).(func() ([]playwright.NameValue, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []playwright.NameValue); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]playwright.NameValue) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// JSON provides a mock function with given fields: v +func (_m *Response) JSON(v interface{}) error { + ret := _m.Called(v) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(v) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Ok provides a mock function with given fields: +func (_m *Response) Ok() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Request provides a mock function with given fields: +func (_m *Response) Request() playwright.Request { + ret := _m.Called() + + var r0 playwright.Request + if rf, ok := ret.Get(0).(func() playwright.Request); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(playwright.Request) + } + } + + return r0 +} + +// SecurityDetails provides a mock function with given fields: +func (_m *Response) SecurityDetails() (*playwright.ResponseSecurityDetailsResult, error) { + ret := _m.Called() + + var r0 *playwright.ResponseSecurityDetailsResult + var r1 error + if rf, ok := ret.Get(0).(func() (*playwright.ResponseSecurityDetailsResult, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() *playwright.ResponseSecurityDetailsResult); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*playwright.ResponseSecurityDetailsResult) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ServerAddr provides a mock function with given fields: +func (_m *Response) ServerAddr() (*playwright.ResponseServerAddrResult, error) { + ret := _m.Called() + + var r0 *playwright.ResponseServerAddrResult + var r1 error + if rf, ok := ret.Get(0).(func() (*playwright.ResponseServerAddrResult, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() *playwright.ResponseServerAddrResult); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*playwright.ResponseServerAddrResult) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Status provides a mock function with given fields: +func (_m *Response) Status() int { + ret := _m.Called() + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// StatusText provides a mock function with given fields: +func (_m *Response) StatusText() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Text provides a mock function with given fields: +func (_m *Response) Text() (string, error) { + ret := _m.Called() + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func() (string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// URL provides a mock function with given fields: +func (_m *Response) URL() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// NewResponse creates a new instance of Response. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewResponse(t interface { + mock.TestingT + Cleanup(func()) +}) *Response { + mock := &Response{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/awsconfig/awsconfig.go b/pkg/awsconfig/awsconfig.go index 4d7317b19..b1bfef9dc 100644 --- a/pkg/awsconfig/awsconfig.go +++ b/pkg/awsconfig/awsconfig.go @@ -1,7 +1,6 @@ package awsconfig import ( - "io/ioutil" "os" "path" "path/filepath" @@ -143,7 +142,7 @@ func (p *CredentialsProvider) ensureConfigExists() error { logger.WithField("dir", dir).Debug("Dir created") // create an base config file - err = ioutil.WriteFile(filename, []byte("["+p.Profile+"]"), 0600) + err = os.WriteFile(filename, []byte("["+p.Profile+"]"), 0600) if err != nil { return err } diff --git a/pkg/cfg/cfg.go b/pkg/cfg/cfg.go index 79b5945b4..8051c8976 100644 --- a/pkg/cfg/cfg.go +++ b/pkg/cfg/cfg.go @@ -27,6 +27,9 @@ const ( // DefaultProfile this is the default profile name used to save the credentials in the aws cli DefaultProfile = "saml" + + // Environment Variable used to define the Keyring Backend for Linux based distro + KeyringBackEnvironmentVariableName = "SAML2AWS_KEYRING_BACKEND" ) // IDPAccount saml IDP account @@ -36,7 +39,10 @@ type IDPAccount struct { URL string `ini:"url"` Username string `ini:"username"` Provider string `ini:"provider"` + BrowserType string `ini:"browser_type,omitempty"` // used by 'Browser' Provider + BrowserExecutablePath string `ini:"browser_executable_path,omitempty"` // used by 'Browser' Provider MFA string `ini:"mfa"` + MFAIPAddress string `ini:"mfa_ip_address"` // used by OneLogin SkipVerify bool `ini:"skip_verify"` Timeout int `ini:"timeout"` AmazonWebservicesURN string `ini:"aws_urn"` @@ -52,8 +58,11 @@ type IDPAccount struct { SAMLCache bool `ini:"saml_cache"` SAMLCacheFile string `ini:"saml_cache_file"` TargetURL string `ini:"target_url"` - DisableRememberDevice bool `ini:"disable_remember_device"` // used by Okta - DisableSessions bool `ini:"disable_sessions"` // used by Okta + DisableRememberDevice bool `ini:"disable_remember_device"` // used by Okta + DisableSessions bool `ini:"disable_sessions"` // used by Okta + DownloadBrowser bool `ini:"download_browser_driver"` // used by browser + BrowserDriverDir string `ini:"browser_driver_dir,omitempty"` // used by browser; hide from user if not set + Headless bool `ini:"headless"` // used by browser Prompter string `ini:"prompter"` } diff --git a/pkg/cfg/cfg_test.go b/pkg/cfg/cfg_test.go index b42802474..5a32a5c37 100644 --- a/pkg/cfg/cfg_test.go +++ b/pkg/cfg/cfg_test.go @@ -13,8 +13,28 @@ func TestNewConfigManagerNew(t *testing.T) { cfgm, err := NewConfigManager("example/saml2aws.ini") require.Nil(t, err) + require.NotNil(t, cfgm) +} + +func TestIDPAccountString(t *testing.T) { + cfgm, err := NewConfigManager("example/saml2aws.ini") + require.Nil(t, err) require.NotNil(t, cfgm) + + idpAccount, err := cfgm.LoadIDPAccount("test123") + require.Nil(t, err) + s := idpAccount.String() + require.Contains(t, s, "urn:amazon:webservices\n") +} + +func TestNewConfigManagerDefaultEmpty(t *testing.T) { + cfgm, err := NewConfigManager("") + require.Nil(t, err) + require.Contains(t, cfgm.configPath, ".saml2aws") + idpAccount, err := cfgm.LoadIDPAccount("foo") + require.Nil(t, err) + require.Equal(t, idpAccount.URL, "") } func TestNewConfigManagerLoad(t *testing.T) { diff --git a/pkg/cookiejar/jar.go b/pkg/cookiejar/jar.go index 62e5b26ec..2c3b569f8 100644 --- a/pkg/cookiejar/jar.go +++ b/pkg/cookiejar/jar.go @@ -18,9 +18,9 @@ import ( ) // PublicSuffixList provides the public suffix of a domain. For example: -// - the public suffix of "example.com" is "com", -// - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and -// - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us". +// - the public suffix of "example.com" is "com", +// - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and +// - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us". // // Implementations of PublicSuffixList must be safe for concurrent use by // multiple goroutines. diff --git a/pkg/cookiejar/jar_test.go b/pkg/cookiejar/jar_test.go index fc1462d0d..5262d2154 100644 --- a/pkg/cookiejar/jar_test.go +++ b/pkg/cookiejar/jar_test.go @@ -20,8 +20,9 @@ var tNow = time.Date(2013, 1, 1, 12, 0, 0, 0, time.UTC) // testPSL implements PublicSuffixList with just two rules: "co.uk" // and the default rule "*". // The implementation has two intentional bugs: -// PublicSuffix("www.buggy.psl") == "xy" -// PublicSuffix("www2.buggy.psl") == "com" +// +// PublicSuffix("www.buggy.psl") == "xy" +// PublicSuffix("www2.buggy.psl") == "com" type testPSL struct{} func (testPSL) String() string { @@ -358,13 +359,13 @@ func mustParseURL(s string) *url.URL { } // jarTest encapsulates the following actions on a jar: -// 1. Perform SetCookies with fromURL and the cookies from setCookies. -// (Done at time tNow + 0 ms.) -// 2. Check that the entries in the jar matches content. -// (Done at time tNow + 1001 ms.) -// 3. For each query in tests: Check that Cookies with toURL yields the -// cookies in want. -// (Query n done at tNow + (n+2)*1001 ms.) +// 1. Perform SetCookies with fromURL and the cookies from setCookies. +// (Done at time tNow + 0 ms.) +// 2. Check that the entries in the jar matches content. +// (Done at time tNow + 1001 ms.) +// 3. For each query in tests: Check that Cookies with toURL yields the +// cookies in want. +// (Query n done at tNow + (n+2)*1001 ms.) type jarTest struct { description string // The description of what this test is supposed to test fromURL string // The full URL of the request from which Set-Cookie headers where received diff --git a/pkg/creds/creds.go b/pkg/creds/creds.go index e006216ba..5aa697103 100644 --- a/pkg/creds/creds.go +++ b/pkg/creds/creds.go @@ -4,6 +4,8 @@ package creds type LoginDetails struct { ClientID string // used by OneLogin ClientSecret string // used by OneLogin + DownloadBrowser bool // used by Browser + MFAIPAddress string // used by OneLogin Username string Password string MFAToken string diff --git a/pkg/flags/flags.go b/pkg/flags/flags.go index 7a3beb6fa..1848358c3 100644 --- a/pkg/flags/flags.go +++ b/pkg/flags/flags.go @@ -12,7 +12,10 @@ type CommonFlags struct { ConfigFile string IdpAccount string IdpProvider string + BrowserType string + BrowserExecutablePath string MFA string + MFAIPAddress string MFAToken string URL string Username string @@ -38,6 +41,7 @@ type CommonFlags struct { // LoginExecFlags flags for the Login / Exec commands type LoginExecFlags struct { CommonFlags *CommonFlags + DownloadBrowser bool Force bool DuoMFAOption string ExecProfile string @@ -71,10 +75,22 @@ func ApplyFlagOverrides(commonFlags *CommonFlags, account *cfg.IDPAccount) { account.Provider = commonFlags.IdpProvider } + if commonFlags.BrowserType != "" { + account.BrowserType = commonFlags.BrowserType + } + + if commonFlags.BrowserExecutablePath != "" { + account.BrowserExecutablePath = commonFlags.BrowserExecutablePath + } + if commonFlags.MFA != "" { account.MFA = commonFlags.MFA } + if commonFlags.MFAIPAddress != "" { + account.MFAIPAddress = commonFlags.MFAIPAddress + } + if commonFlags.AmazonWebservicesURN != "" { account.AmazonWebservicesURN = commonFlags.AmazonWebservicesURN } diff --git a/pkg/page/form_test.go b/pkg/page/form_test.go index 8b0656edc..00bfcdf3a 100644 --- a/pkg/page/form_test.go +++ b/pkg/page/form_test.go @@ -2,8 +2,8 @@ package page import ( "bytes" - "io/ioutil" "net/url" + "os" "testing" "github.com/PuerkitoBio/goquery" @@ -11,7 +11,7 @@ import ( ) func TestNewFormFromDocument(t *testing.T) { - data, err := ioutil.ReadFile("example/multi-form.html") + data, err := os.ReadFile("example/multi-form.html") require.Nil(t, err) doc, err := goquery.NewDocumentFromReader(bytes.NewReader(data)) diff --git a/pkg/prompter/pinentry.go b/pkg/prompter/pinentry.go index eea680ff3..86380fdd6 100644 --- a/pkg/prompter/pinentry.go +++ b/pkg/prompter/pinentry.go @@ -82,6 +82,11 @@ func (p *PinentryPrompter) Password(pr string) string { return p.DefaultPrompter.Password(pr) } +// Display is runniner the default Cli Display +func (p *PinentryPrompter) Display(pr string) { + p.DefaultPrompter.Display(pr) +} + // Run wraps a pinentry run. It sends the query to pinentry via stdin and // reads its stdout to determine the user PIN. // Pinentry uses an Assuan protocol diff --git a/pkg/prompter/pinentry_test.go b/pkg/prompter/pinentry_test.go index 647af8ac0..7c4dfecec 100644 --- a/pkg/prompter/pinentry_test.go +++ b/pkg/prompter/pinentry_test.go @@ -36,6 +36,7 @@ type FakeDefaultPrompter struct { CalledString bool CalledStringRequired bool CalledPassword bool + CalledDisplay bool } // all the functions to implement the Prompter interface @@ -63,7 +64,9 @@ func (f *FakeDefaultPrompter) Password(p string) string { f.CalledPassword = true return "" } - +func (f *FakeDefaultPrompter) Display(p string) { + f.CalledDisplay = true +} func TestValidateAndSetPrompterShouldFailWithWrongInput(t *testing.T) { // backing up the current prompters for the other tests @@ -125,6 +128,9 @@ func TestChecksPinentryPrompterDefault(t *testing.T) { _ = p.Password("random") assert.True(t, fakeDefaultPrompter.CalledPassword) + + p.Display("random") + assert.True(t, fakeDefaultPrompter.CalledDisplay) } func TestChecksPinentryPrompterCallsPinentryForRequestSecurityCode(t *testing.T) { diff --git a/pkg/prompter/prompter.go b/pkg/prompter/prompter.go index 89bcb7791..2637c03b6 100644 --- a/pkg/prompter/prompter.go +++ b/pkg/prompter/prompter.go @@ -16,6 +16,7 @@ type Prompter interface { StringRequired(string) string String(string, string) string Password(string) string + Display(string) } // SetPrompter configure an aternate prompter to the default one @@ -79,3 +80,8 @@ func String(pr string, defaultValue string) string { func Password(pr string) string { return ActivePrompter.Password(pr) } + +// Display prompt, no user input required +func Display(pr string) { + ActivePrompter.Display(pr) +} diff --git a/pkg/prompter/survey.go b/pkg/prompter/survey.go index 686a835fb..2ef5d223d 100644 --- a/pkg/prompter/survey.go +++ b/pkg/prompter/survey.go @@ -3,14 +3,29 @@ package prompter import ( "errors" "fmt" + "os" survey "github.com/AlecAivazis/survey/v2" + survey_terminal "github.com/AlecAivazis/survey/v2/terminal" ) +// outputWriter is where for all prompts will be printed. Defaults to os.Stder. +var outputWriter survey_terminal.FileWriter = os.Stderr + // CliPrompter used to prompt for cli input type CliPrompter struct { } +// SetOutputWriter sets the output writer to use for all survey operations +func SetOutputWriter(writer survey_terminal.FileWriter) { + outputWriter = writer +} + +// stdioOption returns the IO option to use for survey functions +func stdioOption() survey.AskOpt { + return survey.WithStdio(os.Stdin, outputWriter, os.Stderr) +} + // NewCli builds a new cli prompter func NewCli() *CliPrompter { return &CliPrompter{} @@ -22,7 +37,7 @@ func (cli *CliPrompter) RequestSecurityCode(pattern string) string { prompt := &survey.Input{ Message: fmt.Sprintf("Security Token [%s]", pattern), } - _ = survey.AskOne(prompt, &token, survey.WithValidator(survey.Required)) + _ = survey.AskOne(prompt, &token, survey.WithValidator(survey.Required), stdioOption()) return token } @@ -34,7 +49,7 @@ func (cli *CliPrompter) ChooseWithDefault(pr string, defaultValue string, option Options: options, Default: defaultValue, } - _ = survey.AskOne(prompt, &selected, survey.WithValidator(survey.Required)) + _ = survey.AskOne(prompt, &selected, survey.WithValidator(survey.Required), stdioOption()) // return the selected element index for i, option := range options { @@ -52,7 +67,7 @@ func (cli *CliPrompter) Choose(pr string, options []string) int { Message: pr, Options: options, } - _ = survey.AskOne(prompt, &selected, survey.WithValidator(survey.Required)) + _ = survey.AskOne(prompt, &selected, survey.WithValidator(survey.Required), stdioOption()) // return the selected element index for i, option := range options { @@ -63,14 +78,14 @@ func (cli *CliPrompter) Choose(pr string, options []string) int { return 0 } -// StringRequired prompt for string which is required +// String prompt for string with a default func (cli *CliPrompter) String(pr string, defaultValue string) string { val := "" prompt := &survey.Input{ Message: pr, Default: defaultValue, } - _ = survey.AskOne(prompt, &val) + _ = survey.AskOne(prompt, &val, stdioOption()) return val } @@ -80,7 +95,7 @@ func (cli *CliPrompter) StringRequired(pr string) string { prompt := &survey.Input{ Message: pr, } - _ = survey.AskOne(prompt, &val, survey.WithValidator(survey.Required)) + _ = survey.AskOne(prompt, &val, survey.WithValidator(survey.Required), stdioOption()) return val } @@ -90,6 +105,10 @@ func (cli *CliPrompter) Password(pr string) string { prompt := &survey.Password{ Message: pr, } - _ = survey.AskOne(prompt, &val) + _ = survey.AskOne(prompt, &val, stdioOption()) return val } + +func (cli *CliPrompter) Display(pr string) { + _, _ = os.Stderr.WriteString(pr + "\n") +} diff --git a/pkg/provider/aad/aad.go b/pkg/provider/aad/aad.go index 0c2607097..c154f855f 100644 --- a/pkg/provider/aad/aad.go +++ b/pkg/provider/aad/aad.go @@ -6,8 +6,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" - "log" "net/http" "net/url" "strings" @@ -15,13 +13,16 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/versent/saml2aws/v2/pkg/cfg" "github.com/versent/saml2aws/v2/pkg/creds" "github.com/versent/saml2aws/v2/pkg/prompter" "github.com/versent/saml2aws/v2/pkg/provider" - "golang.org/x/net/html" ) +var logger = logrus.WithField("provider", "AzureAD") + // Client wrapper around AzureAD enabling authentication and retrieval of assertions type Client struct { provider.ValidateBase @@ -30,440 +31,76 @@ type Client struct { idpAccount *cfg.IDPAccount } -// Autogenerate startSAML Response struct -// some case, some fields is not exists -type startSAMLResponse struct { - FShowPersistentCookiesWarning bool `json:"fShowPersistentCookiesWarning"` - URLMsaLogout string `json:"urlMsaLogout"` - ShowCantAccessAccountLink bool `json:"showCantAccessAccountLink"` - URLGitHubFed string `json:"urlGitHubFed"` - FShowSignInWithGitHubOnlyOnCredPicker bool `json:"fShowSignInWithGitHubOnlyOnCredPicker"` - FEnableShowResendCode bool `json:"fEnableShowResendCode"` - IShowResendCodeDelay int `json:"iShowResendCodeDelay"` - SSMSCtryPhoneData string `json:"sSMSCtryPhoneData"` - FUseInlinePhoneNumber bool `json:"fUseInlinePhoneNumber"` - URLSessionState string `json:"urlSessionState"` - URLResetPassword string `json:"urlResetPassword"` - URLMsaResetPassword string `json:"urlMsaResetPassword"` - URLLogin string `json:"urlLogin"` - URLSignUp string `json:"urlSignUp"` - URLGetCredentialType string `json:"urlGetCredentialType"` - URLGetOneTimeCode string `json:"urlGetOneTimeCode"` - URLLogout string `json:"urlLogout"` - URLForget string `json:"urlForget"` - URLDisambigRename string `json:"urlDisambigRename"` - URLGoToAADError string `json:"urlGoToAADError"` - URLDssoStatus string `json:"urlDssoStatus"` - URLFidoHelp string `json:"urlFidoHelp"` - URLFidoLogin string `json:"urlFidoLogin"` - URLPostAad string `json:"urlPostAad"` - URLPostMsa string `json:"urlPostMsa"` - URLPIAEndAuth string `json:"urlPIAEndAuth"` - FCBShowSignUp bool `json:"fCBShowSignUp"` - FKMSIEnabled bool `json:"fKMSIEnabled"` - ILoginMode int `json:"iLoginMode"` - FAllowPhoneSignIn bool `json:"fAllowPhoneSignIn"` - FAllowPhoneInput bool `json:"fAllowPhoneInput"` - FAllowSkypeNameLogin bool `json:"fAllowSkypeNameLogin"` - IMaxPollErrors int `json:"iMaxPollErrors"` - IPollingTimeout int `json:"iPollingTimeout"` - SrsSuccess bool `json:"srsSuccess"` - FShowSwitchUser bool `json:"fShowSwitchUser"` - ArrValErrs []string `json:"arrValErrs"` - SErrorCode string `json:"sErrorCode"` - SErrTxt string `json:"sErrTxt"` - SResetPasswordPrefillParam string `json:"sResetPasswordPrefillParam"` - OnPremPasswordValidationConfig struct { - IsUserRealmPrecheckEnabled bool `json:"isUserRealmPrecheckEnabled"` - } `json:"onPremPasswordValidationConfig"` - FSwitchDisambig bool `json:"fSwitchDisambig"` - OCancelPostParams struct { - Error string `json:"error"` - ErrorSubcode string `json:"error_subcode"` - State string `json:"state"` - } `json:"oCancelPostParams"` - IAllowedIdentities int `json:"iAllowedIdentities"` - IRemoteNgcPollingType int `json:"iRemoteNgcPollingType"` - IsGlobalTenant bool `json:"isGlobalTenant"` - FIsFidoSupported bool `json:"fIsFidoSupported"` - FUseNewNoPasswordTypes bool `json:"fUseNewNoPasswordTypes"` - IMaxStackForKnockoutAsyncComponents int `json:"iMaxStackForKnockoutAsyncComponents"` - StrCopyrightTxt string `json:"strCopyrightTxt"` - FShowButtons bool `json:"fShowButtons"` - URLCdn string `json:"urlCdn"` - URLFooterTOU string `json:"urlFooterTOU"` - URLFooterPrivacy string `json:"urlFooterPrivacy"` - URLPost string `json:"urlPost"` - URLRefresh string `json:"urlRefresh"` - URLCancel string `json:"urlCancel"` - IPawnIcon int `json:"iPawnIcon"` - IPollingInterval int `json:"iPollingInterval"` - SPOSTUsername string `json:"sPOST_Username"` - SFT string `json:"sFT"` - SFTName string `json:"sFTName"` - SSessionIdentifierName string `json:"sSessionIdentifierName"` - SCtx string `json:"sCtx"` - IProductIcon int `json:"iProductIcon"` - URLReportPageLoad string `json:"urlReportPageLoad"` - StaticTenantBranding interface{} `json:"staticTenantBranding"` - OAppCobranding struct { - } `json:"oAppCobranding"` - IBackgroundImage int `json:"iBackgroundImage"` - ArrSessions []interface{} `json:"arrSessions"` - FUseConstantPolling bool `json:"fUseConstantPolling"` - FUseFlowTokenAsCanary bool `json:"fUseFlowTokenAsCanary"` - FApplicationInsightsEnabled bool `json:"fApplicationInsightsEnabled"` - IApplicationInsightsEnabledPercentage int `json:"iApplicationInsightsEnabledPercentage"` - URLSetDebugMode string `json:"urlSetDebugMode"` - FEnableCSSAnimation bool `json:"fEnableCssAnimation"` - FAllowGrayOutLightBox bool `json:"fAllowGrayOutLightBox"` - FIsRemoteNGCSupported bool `json:"fIsRemoteNGCSupported"` - Scid int `json:"scid"` - Hpgact int `json:"hpgact"` - Hpgid int `json:"hpgid"` - Pgid string `json:"pgid"` - APICanary string `json:"apiCanary"` - Canary string `json:"canary"` - CorrelationID string `json:"correlationId"` - SessionID string `json:"sessionId"` - Locale struct { - Mkt string `json:"mkt"` - Lcid int `json:"lcid"` - } `json:"locale"` - SlMaxRetry int `json:"slMaxRetry"` - SlReportFailure bool `json:"slReportFailure"` - Strings struct { - Desktopsso struct { - Authenticatingmessage string `json:"authenticatingmessage"` - } `json:"desktopsso"` - } `json:"strings"` - Enums struct { - ClientMetricsModes struct { - None int `json:"None"` - SubmitOnPost int `json:"SubmitOnPost"` - SubmitOnRedirect int `json:"SubmitOnRedirect"` - InstrumentPlt int `json:"InstrumentPlt"` - } `json:"ClientMetricsModes"` - } `json:"enums"` - Urls struct { - Instr struct { - Pageload string `json:"pageload"` - Dssostatus string `json:"dssostatus"` - } `json:"instr"` - } `json:"urls"` - Browser struct { - Ltr int `json:"ltr"` - Other int `json:"_Other"` - Full int `json:"Full"` - REOther int `json:"RE_Other"` - B struct { - Name string `json:"name"` - Major int `json:"major"` - Minor int `json:"minor"` - } `json:"b"` - Os struct { - Name string `json:"name"` - Version string `json:"version"` - } `json:"os"` - V int `json:"V"` - } `json:"browser"` - Watson struct { - URL string `json:"url"` - Bundle string `json:"bundle"` - Sbundle string `json:"sbundle"` - Fbundle string `json:"fbundle"` - ResetErrorPeriod int `json:"resetErrorPeriod"` - MaxCorsErrors int `json:"maxCorsErrors"` - MaxInjectErrors int `json:"maxInjectErrors"` - MaxErrors int `json:"maxErrors"` - MaxTotalErrors int `json:"maxTotalErrors"` - ExpSrcs []string `json:"expSrcs"` - EnvErrorRedirect bool `json:"envErrorRedirect"` - EnvErrorURL string `json:"envErrorUrl"` - } `json:"watson"` - Loader struct { - CdnRoots []string `json:"cdnRoots"` - } `json:"loader"` - ServerDetails struct { - Slc string `json:"slc"` - Dc string `json:"dc"` - Ri string `json:"ri"` - Ver struct { - V []int `json:"v"` - } `json:"ver"` - Rt string `json:"rt"` - Et int `json:"et"` - } `json:"serverDetails"` - Country string `json:"country"` - FBreakBrandingSigninString bool `json:"fBreakBrandingSigninString"` - Bsso struct { - Type string `json:"type"` - Reason string `json:"reason"` - } `json:"bsso"` - URLNoCookies string `json:"urlNoCookies"` - FTrimChromeBssoURL bool `json:"fTrimChromeBssoUrl"` +// Autogenrated Converged Response struct +// for some cases, some fields may not exist +type ConvergedResponse struct { + URLGetCredentialType string `json:"urlGetCredentialType"` + ArrUserProofs []userProof `json:"arrUserProofs"` + URLSkipMfaRegistration string `json:"urlSkipMfaRegistration"` + OPerAuthPollingInterval map[string]float64 `json:"oPerAuthPollingInterval"` + URLBeginAuth string `json:"urlBeginAuth"` + URLEndAuth string `json:"urlEndAuth"` + URLPost string `json:"urlPost"` + SErrorCode string `json:"sErrorCode"` + SErrTxt string `json:"sErrTxt"` + SPOSTUsername string `json:"sPOST_Username"` + SFT string `json:"sFT"` + SFTName string `json:"sFTName"` + SCtx string `json:"sCtx"` + Hpgact int `json:"hpgact"` + Hpgid int `json:"hpgid"` + Pgid string `json:"pgid"` + APICanary string `json:"apiCanary"` + Canary string `json:"canary"` + CorrelationID string `json:"correlationId"` + SessionID string `json:"sessionId"` } -// Autogenerate password login response -// some case, some fields is not exists -type passwordLoginResponse struct { - ArrUserProofs []userProof `json:"arrUserProofs"` - FHideIHaveCodeLink bool `json:"fHideIHaveCodeLink"` - OPerAuthPollingInterval map[string]float64 `json:"oPerAuthPollingInterval"` - FProofIndexedByType bool `json:"fProofIndexedByType"` - URLBeginAuth string `json:"urlBeginAuth"` - URLEndAuth string `json:"urlEndAuth"` - ISAMode int `json:"iSAMode"` - ITrustedDeviceCheckboxConfig int `json:"iTrustedDeviceCheckboxConfig"` - IMaxPollAttempts int `json:"iMaxPollAttempts"` - IPollingTimeout int `json:"iPollingTimeout"` - IPollingBackoffInterval float64 `json:"iPollingBackoffInterval"` - IRememberMfaDuration float64 `json:"iRememberMfaDuration"` - STrustedDeviceCheckboxName string `json:"sTrustedDeviceCheckboxName"` - SAuthMethodInputFieldName string `json:"sAuthMethodInputFieldName"` - ISAOtcLength int `json:"iSAOtcLength"` - ITotpOtcLength int `json:"iTotpOtcLength"` - URLMoreInfo string `json:"urlMoreInfo"` - FShowViewDetailsLink bool `json:"fShowViewDetailsLink"` - FAlwaysUpdateFTInSasEnd bool `json:"fAlwaysUpdateFTInSasEnd"` - IMaxStackForKnockoutAsyncComponents int `json:"iMaxStackForKnockoutAsyncComponents"` - StrCopyrightTxt string `json:"strCopyrightTxt"` - FShowButtons bool `json:"fShowButtons"` - URLCdn string `json:"urlCdn"` - URLFooterTOU string `json:"urlFooterTOU"` - URLFooterPrivacy string `json:"urlFooterPrivacy"` - URLPost string `json:"urlPost"` - URLCancel string `json:"urlCancel"` - IPawnIcon int `json:"iPawnIcon"` - IPollingInterval int `json:"iPollingInterval"` - SPOSTUsername string `json:"sPOST_Username"` - SFT string `json:"sFT"` - SFTName string `json:"sFTName"` - SCtx string `json:"sCtx"` - DynamicTenantBranding []struct { - Locale int `json:"Locale"` - Illustration string `json:"Illustration"` - UserIDLabel string `json:"UserIdLabel"` - KeepMeSignedInDisabled bool `json:"KeepMeSignedInDisabled"` - UseTransparentLightBox bool `json:"UseTransparentLightBox"` - } `json:"dynamicTenantBranding"` - OAppCobranding struct { - } `json:"oAppCobranding"` - IBackgroundImage int `json:"iBackgroundImage"` - FUseConstantPolling bool `json:"fUseConstantPolling"` - FUseFlowTokenAsCanary bool `json:"fUseFlowTokenAsCanary"` - FApplicationInsightsEnabled bool `json:"fApplicationInsightsEnabled"` - IApplicationInsightsEnabledPercentage int `json:"iApplicationInsightsEnabledPercentage"` - URLSetDebugMode string `json:"urlSetDebugMode"` - FEnableCSSAnimation bool `json:"fEnableCssAnimation"` - FAllowGrayOutLightBox bool `json:"fAllowGrayOutLightBox"` - FIsRemoteNGCSupported bool `json:"fIsRemoteNGCSupported"` - Scid int `json:"scid"` - Hpgact int `json:"hpgact"` - Hpgid int `json:"hpgid"` - Pgid string `json:"pgid"` - APICanary string `json:"apiCanary"` - Canary string `json:"canary"` - CorrelationID string `json:"correlationId"` - SessionID string `json:"sessionId"` - Locale struct { - Mkt string `json:"mkt"` - Lcid int `json:"lcid"` - } `json:"locale"` - SlMaxRetry int `json:"slMaxRetry"` - SlReportFailure bool `json:"slReportFailure"` - Strings struct { - Desktopsso struct { - Authenticatingmessage string `json:"authenticatingmessage"` - } `json:"desktopsso"` - } `json:"strings"` - Enums struct { - ClientMetricsModes struct { - None int `json:"None"` - SubmitOnPost int `json:"SubmitOnPost"` - SubmitOnRedirect int `json:"SubmitOnRedirect"` - InstrumentPlt int `json:"InstrumentPlt"` - } `json:"ClientMetricsModes"` - } `json:"enums"` - Urls struct { - Instr struct { - Pageload string `json:"pageload"` - Dssostatus string `json:"dssostatus"` - } `json:"instr"` - } `json:"urls"` - Browser struct { - Ltr int `json:"ltr"` - Other int `json:"_Other"` - Full int `json:"Full"` - REOther int `json:"RE_Other"` - B struct { - Name string `json:"name"` - Major int `json:"major"` - Minor int `json:"minor"` - } `json:"b"` - Os struct { - Name string `json:"name"` - Version string `json:"version"` - } `json:"os"` - V int `json:"V"` - } `json:"browser"` - Watson struct { - URL string `json:"url"` - Bundle string `json:"bundle"` - Sbundle string `json:"sbundle"` - Fbundle string `json:"fbundle"` - ResetErrorPeriod int `json:"resetErrorPeriod"` - MaxCorsErrors int `json:"maxCorsErrors"` - MaxInjectErrors int `json:"maxInjectErrors"` - MaxErrors int `json:"maxErrors"` - MaxTotalErrors int `json:"maxTotalErrors"` - ExpSrcs []string `json:"expSrcs"` - EnvErrorRedirect bool `json:"envErrorRedirect"` - EnvErrorURL string `json:"envErrorUrl"` - } `json:"watson"` - Loader struct { - CdnRoots []string `json:"cdnRoots"` - } `json:"loader"` - ServerDetails struct { - Slc string `json:"slc"` - Dc string `json:"dc"` - Ri string `json:"ri"` - Ver struct { - V []int `json:"v"` - } `json:"ver"` - Rt string `json:"rt"` - Et int `json:"et"` - } `json:"serverDetails"` - Country string `json:"country"` - FBreakBrandingSigninString bool `json:"fBreakBrandingSigninString"` - URLNoCookies string `json:"urlNoCookies"` - FTrimChromeBssoURL bool `json:"fTrimChromeBssoUrl"` +// Autogenerated GetCredentialType Request struct +// for some cases, some fields may not exist +type GetCredentialTypeRequest struct { + Username string `json:"username"` + IsOtherIdpSupported bool `json:"isOtherIdpSupported"` + CheckPhones bool `json:"checkPhones"` + IsRemoteNGCSupported bool `json:"isRemoteNGCSupported"` + IsCookieBannerShown bool `json:"isCookieBannerShown"` + IsFidoSupported bool `json:"isFidoSupported"` + OriginalRequest string `json:"originalRequest"` + Country string `json:"country"` + Forceotclogin bool `json:"forceotclogin"` + IsExternalFederationDisallowed bool `json:"isExternalFederationDisallowed"` + IsRemoteConnectSupported bool `json:"isRemoteConnectSupported"` + FederationFlags int `json:"federationFlags"` + IsSignup bool `json:"isSignup"` + FlowToken string `json:"flowToken"` + IsAccessPassSupported bool `json:"isAccessPassSupported"` } -// Autogenerated skip mfa login response -type SkipMfaResponse struct { - URLPostRedirect string `json:"urlPostRedirect"` - URLSkipMfaRegistration string `json:"urlSkipMfaRegistration"` - URLMoreInfo string `json:"urlMoreInfo"` - SProofUpToken string `json:"sProofUpToken"` - SProofUpTokenName string `json:"sProofUpTokenName"` - SProofUpAuthState string `json:"sProofUpAuthState"` - SCanaryToken string `json:"sCanaryToken"` - IRemainingDaysToSkipMfaRegistration int `json:"iRemainingDaysToSkipMfaRegistration"` - IMaxStackForKnockoutAsyncComponents int `json:"iMaxStackForKnockoutAsyncComponents"` - StrCopyrightTxt string `json:"strCopyrightTxt"` - FShowButtons bool `json:"fShowButtons"` - URLCdn string `json:"urlCdn"` - URLFooterTOU string `json:"urlFooterTOU"` - URLFooterPrivacy string `json:"urlFooterPrivacy"` - URLPost string `json:"urlPost"` - URLCancel string `json:"urlCancel"` - IPawnIcon int `json:"iPawnIcon"` - SPOSTUsername string `json:"sPOST_Username"` - SFT string `json:"sFT"` - SFTName string `json:"sFTName"` - SCanaryTokenName string `json:"sCanaryTokenName"` - DynamicTenantBranding []struct { - Locale int `json:"Locale"` - Illustration string `json:"Illustration"` - UserIDLabel string `json:"UserIdLabel"` - KeepMeSignedInDisabled bool `json:"KeepMeSignedInDisabled"` - UseTransparentLightBox bool `json:"UseTransparentLightBox"` - } `json:"dynamicTenantBranding"` - OAppCobranding struct { - } `json:"oAppCobranding"` - IBackgroundImage int `json:"iBackgroundImage"` - FUseConstantPolling bool `json:"fUseConstantPolling"` - FUseFlowTokenAsCanary bool `json:"fUseFlowTokenAsCanary"` - FApplicationInsightsEnabled bool `json:"fApplicationInsightsEnabled"` - IApplicationInsightsEnabledPercentage int `json:"iApplicationInsightsEnabledPercentage"` - URLSetDebugMode string `json:"urlSetDebugMode"` - FEnableCSSAnimation bool `json:"fEnableCssAnimation"` - FAllowGrayOutLightBox bool `json:"fAllowGrayOutLightBox"` - FIsRemoteNGCSupported bool `json:"fIsRemoteNGCSupported"` - Scid int `json:"scid"` - Hpgact int `json:"hpgact"` - Hpgid int `json:"hpgid"` - Pgid string `json:"pgid"` - APICanary string `json:"apiCanary"` - Canary string `json:"canary"` - CorrelationID string `json:"correlationId"` - SessionID string `json:"sessionId"` - Locale struct { - Mkt string `json:"mkt"` - Lcid int `json:"lcid"` - } `json:"locale"` - SlMaxRetry int `json:"slMaxRetry"` - SlReportFailure bool `json:"slReportFailure"` - Strings struct { - Desktopsso struct { - Authenticatingmessage string `json:"authenticatingmessage"` - } `json:"desktopsso"` - } `json:"strings"` - Enums struct { - ClientMetricsModes struct { - None int `json:"None"` - SubmitOnPost int `json:"SubmitOnPost"` - SubmitOnRedirect int `json:"SubmitOnRedirect"` - InstrumentPlt int `json:"InstrumentPlt"` - } `json:"ClientMetricsModes"` - } `json:"enums"` - Urls struct { - Instr struct { - Pageload string `json:"pageload"` - Dssostatus string `json:"dssostatus"` - } `json:"instr"` - } `json:"urls"` - Browser struct { - Ltr int `json:"ltr"` - Other int `json:"_Other"` - Full int `json:"Full"` - REOther int `json:"RE_Other"` - B struct { - Name string `json:"name"` - Major int `json:"major"` - Minor int `json:"minor"` - } `json:"b"` - Os struct { - Name string `json:"name"` - Version string `json:"version"` - } `json:"os"` - V int `json:"V"` - } `json:"browser"` - Watson struct { - URL string `json:"url"` - Bundle string `json:"bundle"` - Sbundle string `json:"sbundle"` - Fbundle string `json:"fbundle"` - ResetErrorPeriod int `json:"resetErrorPeriod"` - MaxCorsErrors int `json:"maxCorsErrors"` - MaxInjectErrors int `json:"maxInjectErrors"` - MaxErrors int `json:"maxErrors"` - MaxTotalErrors int `json:"maxTotalErrors"` - ExpSrcs []string `json:"expSrcs"` - EnvErrorRedirect bool `json:"envErrorRedirect"` - EnvErrorURL string `json:"envErrorUrl"` - } `json:"watson"` - Loader struct { - CdnRoots []string `json:"cdnRoots"` - } `json:"loader"` - ServerDetails struct { - Slc string `json:"slc"` - Dc string `json:"dc"` - Ri string `json:"ri"` - Ver struct { - V []int `json:"v"` - } `json:"ver"` - Rt string `json:"rt"` - Et int `json:"et"` - } `json:"serverDetails"` - Country string `json:"country"` - FBreakBrandingSigninString bool `json:"fBreakBrandingSigninString"` - URLNoCookies string `json:"urlNoCookies"` - FTrimChromeBssoURL bool `json:"fTrimChromeBssoUrl"` +// Autogenerated GetCredentialType Response struct +// for some cases, some fields may not exist +type GetCredentialTypeResponse struct { + Username string `json:"Username"` + Display string `json:"Display"` + IfExistsResult int `json:"IfExistsResult"` + IsUnmanaged bool `json:"IsUnmanaged"` + ThrottleStatus int `json:"ThrottleStatus"` + Credentials struct { + PrefCredential int `json:"PrefCredential"` + HasPassword bool `json:"HasPassword"` + RemoteNgcParams interface{} `json:"RemoteNgcParams"` + FidoParams interface{} `json:"FidoParams"` + SasParams interface{} `json:"SasParams"` + CertAuthParams interface{} `json:"CertAuthParams"` + GoogleParams interface{} `json:"GoogleParams"` + FacebookParams interface{} `json:"FacebookParams"` + FederationRedirectURL string `json:"FederationRedirectUrl"` + } `json:"Credentials"` + FlowToken string `json:"FlowToken"` + IsSignupDisallowed bool `json:"IsSignupDisallowed"` + APICanary string `json:"apiCanary"` } -// mfa request +// MFA Request struct type mfaRequest struct { AuthMethodID string `json:"AuthMethodId"` Method string `json:"Method"` @@ -473,7 +110,7 @@ type mfaRequest struct { AdditionalAuthData string `json:"AdditionalAuthData,omitempty"` } -// mfa response +// MFA Response struct type mfaResponse struct { Success bool `json:"Success"` ResultValue string `json:"ResultValue"` @@ -486,122 +123,7 @@ type mfaResponse struct { SessionID string `json:"SessionId"` CorrelationID string `json:"CorrelationId"` Timestamp time.Time `json:"Timestamp"` -} - -// Autogenerate ProcessAuth response -// some case, some fields is not exists -type processAuthResponse struct { - IMaxStackForKnockoutAsyncComponents int `json:"iMaxStackForKnockoutAsyncComponents"` - StrCopyrightTxt string `json:"strCopyrightTxt"` - FShowButtons bool `json:"fShowButtons"` - URLCdn string `json:"urlCdn"` - URLFooterTOU string `json:"urlFooterTOU"` - URLFooterPrivacy string `json:"urlFooterPrivacy"` - URLPost string `json:"urlPost"` - IPawnIcon int `json:"iPawnIcon"` - SPOSTUsername string `json:"sPOST_Username"` - SFT string `json:"sFT"` - SFTName string `json:"sFTName"` - SCtx string `json:"sCtx"` - SCanaryTokenName string `json:"sCanaryTokenName"` - DynamicTenantBranding []struct { - Locale int `json:"Locale"` - Illustration string `json:"Illustration"` - UserIDLabel string `json:"UserIdLabel"` - KeepMeSignedInDisabled bool `json:"KeepMeSignedInDisabled"` - UseTransparentLightBox bool `json:"UseTransparentLightBox"` - } `json:"dynamicTenantBranding"` - OAppCobranding struct { - } `json:"oAppCobranding"` - IBackgroundImage int `json:"iBackgroundImage"` - FUseConstantPolling bool `json:"fUseConstantPolling"` - FUseFlowTokenAsCanary bool `json:"fUseFlowTokenAsCanary"` - FApplicationInsightsEnabled bool `json:"fApplicationInsightsEnabled"` - IApplicationInsightsEnabledPercentage int `json:"iApplicationInsightsEnabledPercentage"` - URLSetDebugMode string `json:"urlSetDebugMode"` - FEnableCSSAnimation bool `json:"fEnableCssAnimation"` - FAllowGrayOutLightBox bool `json:"fAllowGrayOutLightBox"` - FIsRemoteNGCSupported bool `json:"fIsRemoteNGCSupported"` - Scid int `json:"scid"` - Hpgact int `json:"hpgact"` - Hpgid int `json:"hpgid"` - Pgid string `json:"pgid"` - APICanary string `json:"apiCanary"` - Canary string `json:"canary"` - CorrelationID string `json:"correlationId"` - SessionID string `json:"sessionId"` - Locale struct { - Mkt string `json:"mkt"` - Lcid int `json:"lcid"` - } `json:"locale"` - SlMaxRetry int `json:"slMaxRetry"` - SlReportFailure bool `json:"slReportFailure"` - Strings struct { - Desktopsso struct { - Authenticatingmessage string `json:"authenticatingmessage"` - } `json:"desktopsso"` - } `json:"strings"` - Enums struct { - ClientMetricsModes struct { - None int `json:"None"` - SubmitOnPost int `json:"SubmitOnPost"` - SubmitOnRedirect int `json:"SubmitOnRedirect"` - InstrumentPlt int `json:"InstrumentPlt"` - } `json:"ClientMetricsModes"` - } `json:"enums"` - Urls struct { - Instr struct { - Pageload string `json:"pageload"` - Dssostatus string `json:"dssostatus"` - } `json:"instr"` - } `json:"urls"` - Browser struct { - Ltr int `json:"ltr"` - Other int `json:"_Other"` - Full int `json:"Full"` - REOther int `json:"RE_Other"` - B struct { - Name string `json:"name"` - Major int `json:"major"` - Minor int `json:"minor"` - } `json:"b"` - Os struct { - Name string `json:"name"` - Version string `json:"version"` - } `json:"os"` - V int `json:"V"` - } `json:"browser"` - Watson struct { - URL string `json:"url"` - Bundle string `json:"bundle"` - Sbundle string `json:"sbundle"` - Fbundle string `json:"fbundle"` - ResetErrorPeriod int `json:"resetErrorPeriod"` - MaxCorsErrors int `json:"maxCorsErrors"` - MaxInjectErrors int `json:"maxInjectErrors"` - MaxErrors int `json:"maxErrors"` - MaxTotalErrors int `json:"maxTotalErrors"` - ExpSrcs []string `json:"expSrcs"` - EnvErrorRedirect bool `json:"envErrorRedirect"` - EnvErrorURL string `json:"envErrorUrl"` - } `json:"watson"` - Loader struct { - CdnRoots []string `json:"cdnRoots"` - } `json:"loader"` - ServerDetails struct { - Slc string `json:"slc"` - Dc string `json:"dc"` - Ri string `json:"ri"` - Ver struct { - V []int `json:"v"` - } `json:"ver"` - Rt string `json:"rt"` - Et int `json:"et"` - } `json:"serverDetails"` - Country string `json:"country"` - FBreakBrandingSigninString bool `json:"fBreakBrandingSigninString"` - URLNoCookies string `json:"urlNoCookies"` - FTrimChromeBssoURL bool `json:"fTrimChromeBssoUrl"` + Entropy int `json:"Entropy"` } // A given method for a user to prove their indentity @@ -633,337 +155,299 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { // Authenticate to AzureAD and return the data from the body of the SAML assertion. func (ac *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) { - var samlAssertion string var res *http.Response + var err error + var resBody []byte + var resBodyStr string + var convergedResponse *ConvergedResponse // idpAccount.URL = https://account.activedirectory.windowsazure.com // startSAML startURL := fmt.Sprintf("%s/applications/redirecttofederatedapplication.aspx?Operation=LinkedSignIn&applicationId=%s", ac.idpAccount.URL, ac.idpAccount.AppID) - res, err := ac.client.Get(startURL) + res, err = ac.client.Get(startURL) if err != nil { - return samlAssertion, errors.Wrap(err, "error retrieving form") + return samlAssertion, errors.Wrap(err, "error retrieving entry URL") + } + +AuthProcessor: + for { + resBody, _ = io.ReadAll(res.Body) + resBodyStr = string(resBody) + // reset res.Body so it can be read again later if required + res.Body = io.NopCloser(bytes.NewBuffer(resBody)) + + switch { + case strings.Contains(resBodyStr, "ConvergedSignIn"): + logger.Debug("processing ConvergedSignIn") + res, err = ac.processConvergedSignIn(res, resBodyStr, loginDetails) + case strings.Contains(resBodyStr, "ConvergedProofUpRedirect"): + logger.Debug("processing ConvergedProofUpRedirect") + res, err = ac.processConvergedProofUpRedirect(res, resBodyStr) + case strings.Contains(resBodyStr, "KmsiInterrupt"): + logger.Debug("processing KmsiInterrupt") + res, err = ac.processKmsiInterrupt(res, resBodyStr) + case strings.Contains(resBodyStr, "ConvergedTFA"): + logger.Debug("processing ConvergedTFA") + res, err = ac.processConvergedTFA(res, resBodyStr) + case strings.Contains(resBodyStr, "SAMLRequest"): + logger.Debug("processing SAMLRequest") + res, err = ac.processSAMLRequest(res, resBodyStr) + case ac.isHiddenForm(resBodyStr): + if samlAssertion, _ = ac.getSamlAssertion(resBodyStr); samlAssertion != "" { + logger.Debug("processing a SAMLResponse") + return samlAssertion, nil + } + logger.Debug("processing a 'hiddenform'") + res, err = ac.reProcessForm(resBodyStr) + default: + if strings.Contains(resBodyStr, "$Config") { + if err := ac.unmarshalEmbeddedJson(resBodyStr, &convergedResponse); err != nil { + return samlAssertion, errors.Wrap(err, "unmarshal error") + } + logger.Debug("unknown process step found:", convergedResponse.Pgid) + } else { + logger.Debug("reached an unknown page within the authentication process") + } + break AuthProcessor + } + if err != nil { + return samlAssertion, err + } } - // data is embedded javascript object - // - */ - isEnabledConditonalAccess := strings.HasPrefix(resBodyStr, "Working...") && strings.Contains(resBodyStr, "name=\"flowtoken\"") - - if isSkippedMFA || isEnabledConditonalAccess { - // require reprocess - if strings.Contains(resBodyStr, " - var loginPasswordJson string - if strings.Contains(resBodyStr, "$Config") { - loginPasswordJson = ac.getJsonFromConfig(resBodyStr) + if federationRedirectURL != "" { + res, err = ac.processADFSAuthentication(federationRedirectURL, loginDetails) + if err != nil { + return res, err } - resBodyStr, err = ac.processAuth(loginPasswordJson, res) + } else { + res, err = ac.processAuthentication(loginRequestUrl, refererUrl, loginDetails, convergedResponse) if err != nil { - return samlAssertion, err + return res, err } } - node, _ := html.Parse(strings.NewReader(resBodyStr)) - doc := goquery.NewDocumentFromNode(node) - - // data in input tag - authForm := url.Values{} - var authSubmitURL string - - doc.Find("input").Each(func(i int, s *goquery.Selection) { - name, ok := s.Attr("name") - if !ok { - return - } - value, ok := s.Attr("value") - if !ok { - return - } - authForm.Set(name, value) - }) - - doc.Find("form").Each(func(i int, s *goquery.Selection) { - action, ok := s.Attr("action") - if !ok { - return - } - authSubmitURL = action - }) + return res, nil +} - if authSubmitURL == "" { - return samlAssertion, fmt.Errorf("unable to locate IDP oidc form submit URL") +func (ac *Client) requestGetCredentialType(refererUrl string, loginDetails *creds.LoginDetails, convergedResponse *ConvergedResponse) (GetCredentialTypeResponse, *http.Response, error) { + var res *http.Response + var getCredentialTypeResponse GetCredentialTypeResponse + + reqBodyObj := GetCredentialTypeRequest{ + Username: loginDetails.Username, + IsOtherIdpSupported: true, + CheckPhones: false, + IsRemoteNGCSupported: false, + IsCookieBannerShown: false, + IsFidoSupported: false, + OriginalRequest: convergedResponse.SCtx, + FlowToken: convergedResponse.SFT, + } + reqBodyJson, err := json.Marshal(reqBodyObj) + if err != nil { + return getCredentialTypeResponse, res, errors.Wrap(err, "failed to build GetCredentialType request JSON") } - req, err := http.NewRequest("POST", authSubmitURL, strings.NewReader(authForm.Encode())) + req, err := http.NewRequest("POST", convergedResponse.URLGetCredentialType, strings.NewReader(string(reqBodyJson))) if err != nil { - return samlAssertion, errors.Wrap(err, "error building authentication request") + return getCredentialTypeResponse, res, errors.Wrap(err, "error building GetCredentialType request") } - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("canary", convergedResponse.APICanary) + req.Header.Add("client-request-id", convergedResponse.CorrelationID) + req.Header.Add("hpgact", fmt.Sprint(convergedResponse.Hpgact)) + req.Header.Add("hpgid", fmt.Sprint(convergedResponse.Hpgid)) + req.Header.Add("hpgrequestid", convergedResponse.SessionID) + req.Header.Add("Referer", refererUrl) - ac.client.EnableFollowRedirect() res, err = ac.client.Do(req) if err != nil { - return samlAssertion, errors.Wrap(err, "error retrieving oidc login form results") + return getCredentialTypeResponse, res, errors.Wrap(err, "error retrieving GetCredentialType results") } - // get saml assertion - oidcResponse, err := ioutil.ReadAll(res.Body) + err = json.NewDecoder(res.Body).Decode(&getCredentialTypeResponse) if err != nil { - return samlAssertion, errors.Wrap(err, "oidc login response error") + return getCredentialTypeResponse, res, errors.Wrap(err, "error decoding GetCredentialType results") } - oidcResponseStr := string(oidcResponse) + return getCredentialTypeResponse, res, nil +} - // data is embedded javascript - // window.location = 'https:/..../?SAMLRequest=......' - oidcResponseList := strings.Split(oidcResponseStr, ";") - var SAMLRequestURL string - for _, v := range oidcResponseList { - if strings.Contains(v, "SAMLRequest") { - startURLPos := strings.Index(v, "https://") - endURLPos := strings.Index(v[startURLPos:], "'") - if endURLPos == -1 { - endURLPos = strings.Index(v[startURLPos:], "\"") - } - SAMLRequestURL = v[startURLPos : startURLPos+endURLPos] - } +func (ac *Client) processADFSAuthentication(federationUrl string, loginDetails *creds.LoginDetails) (*http.Response, error) { + var res *http.Response + var err error + var resBodyStr string + var formValues url.Values + var formSubmitUrl string + var req *http.Request + res, err = ac.client.Get(federationUrl) + if err != nil { + return res, errors.Wrap(err, "error retrieving ADFS url") } - if SAMLRequestURL == "" { - return samlAssertion, fmt.Errorf("unable to locate SAMLRequest URL") - } - req, err = http.NewRequest("GET", SAMLRequestURL, nil) + resBodyStr, _ = ac.responseBodyAsString(res.Body) + + formValues, formSubmitUrl, err = ac.reSubmitFormData(resBodyStr) if err != nil { - return samlAssertion, errors.Wrap(err, "error building get request") + return res, errors.Wrap(err, "failed to parse ADFS login form") } - res, err = ac.client.Do(req) - if err != nil { - return samlAssertion, errors.Wrap(err, "error retrieving oidc login form results") + if formSubmitUrl == "" { + return res, fmt.Errorf("unable to locate ADFS form submit URL") } - // if mfa skipped then get $Config and urlSkipMfaRegistration - // get urlSkipMfaRegistraition to return saml assertion - resBodyStr, err = ac.responseBodyAsString(res.Body) + formValues.Set("UserName", loginDetails.Username) + formValues.Set("Password", loginDetails.Password) + formValues.Set("AuthMethod", "FormsAuthentication") + + req, err = http.NewRequest("POST", ac.fullUrl(res, formSubmitUrl), strings.NewReader(formValues.Encode())) if err != nil { - return samlAssertion, errors.Wrap(err, "error oidc login response read") - } - if strings.Contains(resBodyStr, "arrUserProofs") { - // data is embedded javascript object - // + + + + + + + + + +
+

JavaScript required

+

JavaScript is required. This web browser does not support JavaScript or JavaScript in this web browser is not enabled.

+

To find out if your web browser supports JavaScript or to enable JavaScript, see web browser help.

+
+ +
+
+
+
+
+
+ +
+
+ +
+ + + +
+
Sign in
+ +
+
+ +
+ +
+
+ + +
+ +
+ + +
+ +
+ Sign in +
+
+ +
+ +
+
+ + + + +
+
+ +
+ introduction +
+ + +
+ +
+ +
+
+
+
+
+ +
+
+
+ + + + + + diff --git a/pkg/provider/aad/testdata/ADFStrust.html b/pkg/provider/aad/testdata/ADFStrust.html new file mode 100644 index 000000000..4819c5a5f --- /dev/null +++ b/pkg/provider/aad/testdata/ADFStrust.html @@ -0,0 +1 @@ +Working...
\ No newline at end of file diff --git a/pkg/provider/aad/testdata/BeginAuth.json b/pkg/provider/aad/testdata/BeginAuth.json new file mode 100644 index 000000000..21b6abc0e --- /dev/null +++ b/pkg/provider/aad/testdata/BeginAuth.json @@ -0,0 +1 @@ +{"Success":true,"ResultValue":"Success","Message":null,"AuthMethodId":"{{.AuthMethodId}}","ErrCode":0,"Retry":false,"FlowToken":"{{.SFT}}","Ctx":"{{.Ctx}}","SessionId":"{{.SessionId}}","CorrelationId":"{{.ClientRequestId}}","Timestamp":"2020-01-01T00:00:00Z","Entropy":{{.Entropy}}} \ No newline at end of file diff --git a/pkg/provider/aad/testdata/ConvergedProofUpRedirect.html b/pkg/provider/aad/testdata/ConvergedProofUpRedirect.html new file mode 100644 index 000000000..434af1199 --- /dev/null +++ b/pkg/provider/aad/testdata/ConvergedProofUpRedirect.html @@ -0,0 +1,73 @@ + + + + + + + Sign in to your account + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/provider/aad/testdata/ConvergedSignIn.html b/pkg/provider/aad/testdata/ConvergedSignIn.html new file mode 100644 index 000000000..5d1779633 --- /dev/null +++ b/pkg/provider/aad/testdata/ConvergedSignIn.html @@ -0,0 +1,75 @@ + + + + + + + Sign in to your account + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/provider/aad/testdata/ConvergedTFA.html b/pkg/provider/aad/testdata/ConvergedTFA.html new file mode 100644 index 000000000..f55cb0400 --- /dev/null +++ b/pkg/provider/aad/testdata/ConvergedTFA.html @@ -0,0 +1,74 @@ + + + + + + + Sign in to your account + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/provider/aad/testdata/EndAuth.json b/pkg/provider/aad/testdata/EndAuth.json new file mode 100644 index 000000000..21b6abc0e --- /dev/null +++ b/pkg/provider/aad/testdata/EndAuth.json @@ -0,0 +1 @@ +{"Success":true,"ResultValue":"Success","Message":null,"AuthMethodId":"{{.AuthMethodId}}","ErrCode":0,"Retry":false,"FlowToken":"{{.SFT}}","Ctx":"{{.Ctx}}","SessionId":"{{.SessionId}}","CorrelationId":"{{.ClientRequestId}}","Timestamp":"2020-01-01T00:00:00Z","Entropy":{{.Entropy}}} \ No newline at end of file diff --git a/pkg/provider/aad/testdata/GetCredentialType_adfs.json b/pkg/provider/aad/testdata/GetCredentialType_adfs.json new file mode 100644 index 000000000..adadb936f --- /dev/null +++ b/pkg/provider/aad/testdata/GetCredentialType_adfs.json @@ -0,0 +1 @@ +{"Username":"{{.UserName}}","Display":"{{.UserName}}","IfExistsResult":0,"IsUnmanaged":false,"ThrottleStatus":1,"Credentials":{"PrefCredential":4,"HasPassword":true,"RemoteNgcParams":null,"FidoParams":null,"SasParams":null,"CertAuthParams":null,"GoogleParams":null,"FacebookParams":null,"FederationRedirectUrl":"{{.UrlFederationRedirect}}"},"EstsProperties":{"UserTenantBranding":[{"Locale":0,"BannerLogo":"https://via.placeholder.com/280x60.png","TileLogo":"https://via.placeholder.com/240x240.png","TileDarkLogo":"https://via.placeholder.com/240x240.png","UserIdLabel":"someone@example.com","KeepMeSignedInDisabled":false,"UseTransparentLightBox":false,"LayoutTemplateConfig":{"showHeader":false,"headerLogo":"","layoutType":0,"hideCantAccessYourAccount":false,"hideForgotMyPassword":false,"hideResetItNow":false,"hideAccountResetCredentials":false,"showFooter":true,"hideTOU":false,"hidePrivacy":false},"CustomizationFiles":{"strings":{"adminConsent":"","attributeCollection":"","authenticatorNudgeScreen":"","conditionalAccess":""},"customCssUrl":""}}],"DomainType":4},"FlowToken":"{{.SFT}}","IsSignupDisallowed":true,"apiCanary":"{{.ApiCanary}}"} \ No newline at end of file diff --git a/pkg/provider/aad/testdata/GetCredentialType_default.json b/pkg/provider/aad/testdata/GetCredentialType_default.json new file mode 100644 index 000000000..ef5811a92 --- /dev/null +++ b/pkg/provider/aad/testdata/GetCredentialType_default.json @@ -0,0 +1 @@ +{"Username":"{{.UserName}}","Display":"{{.UserName}}","IfExistsResult":0,"IsUnmanaged":false,"ThrottleStatus":0,"Credentials":{"PrefCredential":1,"HasPassword":true,"RemoteNgcParams":null,"FidoParams":null,"SasParams":null,"CertAuthParams":null,"GoogleParams":null,"FacebookParams":null},"EstsProperties":{"UserTenantBranding":null,"DomainType":3},"FlowToken":"{{.SFT}}","IsSignupDisallowed":true,"apiCanary":"{{.ApiCanary}}"} \ No newline at end of file diff --git a/pkg/provider/aad/testdata/HiddenForm.html b/pkg/provider/aad/testdata/HiddenForm.html new file mode 100644 index 000000000..b47150d3c --- /dev/null +++ b/pkg/provider/aad/testdata/HiddenForm.html @@ -0,0 +1 @@ +Working...
\ No newline at end of file diff --git a/pkg/provider/aad/testdata/KmsiInterrupt.html b/pkg/provider/aad/testdata/KmsiInterrupt.html new file mode 100644 index 000000000..a4eb86a1d --- /dev/null +++ b/pkg/provider/aad/testdata/KmsiInterrupt.html @@ -0,0 +1,73 @@ + + + + + + + Sign in to your account + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/provider/aad/testdata/LoginEmbeddedJsonExtraJavascript.html b/pkg/provider/aad/testdata/LoginEmbeddedJsonExtraJavascript.html new file mode 100644 index 000000000..c3c6f5775 --- /dev/null +++ b/pkg/provider/aad/testdata/LoginEmbeddedJsonExtraJavascript.html @@ -0,0 +1,12 @@ + + + + + + + diff --git a/pkg/provider/aad/testdata/LoginEmbeddedJsonLineBreak.html b/pkg/provider/aad/testdata/LoginEmbeddedJsonLineBreak.html new file mode 100644 index 000000000..6b6795d05 --- /dev/null +++ b/pkg/provider/aad/testdata/LoginEmbeddedJsonLineBreak.html @@ -0,0 +1,12 @@ + + + + + + + diff --git a/pkg/provider/aad/testdata/LoginEmbeddedJsonNoLineBreak.html b/pkg/provider/aad/testdata/LoginEmbeddedJsonNoLineBreak.html new file mode 100644 index 000000000..b617f1f6e --- /dev/null +++ b/pkg/provider/aad/testdata/LoginEmbeddedJsonNoLineBreak.html @@ -0,0 +1,11 @@ + + + + + + + diff --git a/pkg/provider/aad/testdata/SAMLRequest.html b/pkg/provider/aad/testdata/SAMLRequest.html new file mode 100644 index 000000000..9d39c270b --- /dev/null +++ b/pkg/provider/aad/testdata/SAMLRequest.html @@ -0,0 +1,211 @@ + + + + + + + + + + + + +
+
+ + + +
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+
+
+ +
+ + + + + +
+ +
+
+
+
+
+ +
+ +
+
+
+ + + + + + + + +
+ + + + diff --git a/pkg/provider/aad/testdata/SAMLResponse.html b/pkg/provider/aad/testdata/SAMLResponse.html new file mode 100644 index 000000000..be028ef01 --- /dev/null +++ b/pkg/provider/aad/testdata/SAMLResponse.html @@ -0,0 +1 @@ +Working...
\ No newline at end of file diff --git a/pkg/provider/aad/testdata/SAMLResponse.xml b/pkg/provider/aad/testdata/SAMLResponse.xml new file mode 100644 index 000000000..aaba12d27 --- /dev/null +++ b/pkg/provider/aad/testdata/SAMLResponse.xml @@ -0,0 +1,94 @@ + + https://sts.windows.net/25f4519b-eca5-405d-b516-123af862c268/ + + + + + + https://sts.windows.net/25f4519b-eca5-405d-b516-123af862c268/ + + + + + + + + + + + ba0vLeerzPU5SlBzNMQ95WNLauGojdAZBDdCPMJmUNI= + + + QE4db1da3PKU583Q0mm9MRpLaogENs95eWSkc8RvtU3kYwOVHpFtcVyG0wti54sc72V7rWSr3UoIGysgx_-3UJch_oG1JJi7IdNLhbFBx-PVxtAKvIdMkSM8tXRLuEtkNUB760jQAmie43Che8j47JdyWp4nh19QTDHjpH2vW9zldp-mhlLtl_QQQ-lJPd-LWC3A4xS0a81fenApzq4KvOY4zghapNih_dZOH6OO_UBgq_fyZ-x7gDiHin4UeySsaHQEBPr_mx5t6ilteSjm3J6HKlVVw9HNhmgry80UJkuVZ-7nWfgaawNjHDtG2UXN9k5oT0hCokMG7SlcPVKLqA== + + + MIIC8DCCAdigAwIBAgIQV3utGUh+Q55I54g7Y8RkUjANBgkqhkiG9w0BAQsFADA0MTIwMAYDVQQDEylNaWNyb3NvZnQgQXp1cmUgRmVkZXJhdGVkIFNTTyBDZXJ0aWZpY2F0ZTAeFw0xOTA4MDgwNzA1MDBaFw0yMjA4MDgwNzAyMTlaMDQxMjAwBgNVBAMTKU1pY3Jvc29mdCBBenVyZSBGZWRlcmF0ZWQgU1NPIENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApO5wQVjdzEx2/j5gNrUh94wpBni4wNmRI3tKoSWJpRjnnlhwHSiiAWo7KX924owbbc8m+aqZ/dt7gdyADl02dJN5vjYwHy0rJoitC6j9hVHd/Fz7QOOhlaLwtxKfp7bgzvLYw3/HsAFbnJxwQWdddiPm6+2b903tdUehV9lR7LgLwa8pYA8ybnV/8KrgB9zwDi8c+h0Od3+SLvheCagOLmPZBc3u2YkW6BRLt3HIdT75Rv5G81ak3yKdmpjelIgcj/39x/g5K4xTYYJz8x/a8xdy1tax46Vr0h7xfg3YkuYy/kcs6JGilQEVsA/NVmAGPl7W7uu03CCFsi5Xc8aIXwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBiVFSvDRTGqlgnBaQjrdaN3GanD+vggrz8rzm+ccdFi72xkRKMmAxePVcgYNZWl4pZgXitQOa9otE4gxzLFQEXpShj/xgomZ1orF5Fx2DIP/TtHn+6BGK4pi/QsSDWqOx33lDnPjXY6Ouiyz4GoY50l6UfXzwyCiYBoI/r0Paf5bLSF9gV0aJInFswG28lXDsUydXKsByrprqvYpWX6lplRf/SgCmCf8l9eApk+558cWtIlUn1mDzYxt8z7X8xhBYXyg6193wz4A2ULhfB7No/bO6WDlaaK2YN1VSpjRdwDKpKiR2yy3kJRJl1IO8szqIYPKcrdTwGBNRDix1UEwdR + + + + + exampleuser@exampledomain.com + + + + + + + https://signin.aws.amazon.com/saml + + + + + 25f4519b-eca5-405d-b516-123af862c268 + + + 5159e491-c7a1-4c67-bff9-666cdab9a60b + + + Doe, John + + + https://sts.windows.net/25f4519b-eca5-405d-b516-123af862c268/ + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + http://schemas.microsoft.com/claims/multipleauthn + + + arn:aws:iam::012345678901:role/example_role,arn:aws:iam::012345678901:saml-provider/EXAMPLE_PROVIDER + arn:aws:iam::123456789012:role/example_role,arn:aws:iam::123456789012:saml-provider/EXAMPLE_PROVIDER + + + John + + + Doe + + + john.doe@exampledomain.com + + + exampleuser@exampledomain.com + + + arn:aws:iam::012345678901:role/example_role,arn:aws:iam::012345678901:saml-provider/EXAMPLE_PROVIDER + arn:aws:iam::123456789012:role/example_role,arn:aws:iam::123456789012:saml-provider/EXAMPLE_PROVIDER + + + exampleuser@exampledomain.com + + + exampleuser + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + + \ No newline at end of file diff --git a/pkg/provider/adfs/adfs.go b/pkg/provider/adfs/adfs.go index a150299af..a908ff8e7 100644 --- a/pkg/provider/adfs/adfs.go +++ b/pkg/provider/adfs/adfs.go @@ -86,8 +86,34 @@ func (ac *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) authSubmitURL = action }) + // Trim whitespace and discard empty values from Kmsi field + if val, ok := authForm["Kmsi"]; ok { + var trimmedKmsi []string + var t string + for _, s := range val { + t = strings.TrimSpace(s) + if len(t) > 0 { + trimmedKmsi = append(trimmedKmsi, t) + } + } + authForm["Kmsi"] = trimmedKmsi + } + if authSubmitURL == "" { return samlAssertion, fmt.Errorf("unable to locate IDP authentication form submit URL") + } else if strings.HasPrefix(authSubmitURL, "/") { + // + // The server returned a relative URL. Make it absolute. + // + parsedUrl, err := url.Parse(adfsURL) + if err != nil { + return "", errors.Wrap(err, "failed to parse ADFS URL") + } + parsedPath, err := url.Parse(authSubmitURL) + if err != nil { + return "", errors.Wrap(err, "failed to parse authSubmitURL fragment") + } + authSubmitURL = parsedUrl.ResolveReference(parsedPath).String() } doc, err = ac.submit(authSubmitURL, authForm) @@ -122,7 +148,15 @@ func (ac *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) doc.Find("input").Each(func(i int, s *goquery.Selection) { updatePassthroughFormData(azureForm, s) }) - sel := doc.Find("p#instructions") + sel := doc.Find("p#validEntropyNumber") + if sel.Index() != -1 { + if instructions != sel.Text() { + instructions = sel.Text() + log.Println("Open your Microsoft Authenticator app and tap the number you see below to sign in.") + log.Println(instructions) + } + } + sel = doc.Find("p#instructions") if sel.Index() != -1 { if instructions != sel.Text() { instructions = sel.Text() diff --git a/pkg/provider/adfs2/adfs2_test.go b/pkg/provider/adfs2/adfs2_test.go new file mode 100644 index 000000000..c650926e6 --- /dev/null +++ b/pkg/provider/adfs2/adfs2_test.go @@ -0,0 +1,69 @@ +package adfs2 + +import ( + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/versent/saml2aws/v2/mocks" + "github.com/versent/saml2aws/v2/pkg/cfg" + "github.com/versent/saml2aws/v2/pkg/creds" + "github.com/versent/saml2aws/v2/pkg/prompter" +) + +func TestADFS2RSA(t *testing.T) { + svr := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authresp1 := fmt.Sprintf(`
+ + + +
`, r.Host) + passcoderesp1 := fmt.Sprintf(`
+ + + +
`, r.Host) + rsaresp1 := fmt.Sprintf(`
+ + +
`, r.Host) + if strings.HasPrefix(r.URL.String(), "/adfs/ls/IdpInitiatedSignOn.aspx") { + _, err := w.Write([]byte(authresp1)) + assert.Nil(t, err) + } else if strings.HasPrefix(r.URL.String(), "/authpost1") { + _, err := w.Write([]byte(passcoderesp1)) + assert.Nil(t, err) + } else if strings.HasPrefix(r.URL.String(), "/passcodepost1") { + _, err := w.Write([]byte(rsaresp1)) + assert.Nil(t, err) + } else { + t.Fatalf("unexpected %v", r) + } + })) + defer svr.Close() + idpAccount := cfg.NewIDPAccount() + idpAccount.URL = svr.URL + idpAccount.MFA = "RSA" + idpAccount.Username = "user@example.com" + idpAccount.SkipVerify = true + + loginDetails := &creds.LoginDetails{ + Username: idpAccount.Username, + Password: "abc123", + URL: idpAccount.URL, + } + + pr := &mocks.Prompter{} + prompter.SetPrompter(pr) + pr.Mock.On("Password", "Enter nextCode").Return("5309") + pr.Mock.On("Password", "Enter passcode").Return("0953") + + ac, err := New(idpAccount) + assert.Nil(t, err) + resp, err := ac.Authenticate(loginDetails) + assert.Nil(t, err) + assert.Equal(t, resp, "saml1") +} diff --git a/pkg/provider/adfs2/ntlm.go b/pkg/provider/adfs2/ntlm.go index cd51ad2d3..f78696e8a 100644 --- a/pkg/provider/adfs2/ntlm.go +++ b/pkg/provider/adfs2/ntlm.go @@ -3,7 +3,7 @@ package adfs2 import ( "bytes" "fmt" - "io/ioutil" + "io" "net/http" "github.com/PuerkitoBio/goquery" @@ -30,7 +30,7 @@ func (ac *Client) authenticateNTLM(loginDetails *creds.LoginDetails) (string, er return "", errors.Wrap(err, "error retieving login form") } - data, err := ioutil.ReadAll(res.Body) + data, err := io.ReadAll(res.Body) if err != nil { return "", errors.Wrap(err, "error retieving body") } diff --git a/pkg/provider/adfs2/rsa.go b/pkg/provider/adfs2/rsa.go index 5dca411cc..dfd11efc8 100644 --- a/pkg/provider/adfs2/rsa.go +++ b/pkg/provider/adfs2/rsa.go @@ -3,7 +3,7 @@ package adfs2 import ( "bytes" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "strings" @@ -30,7 +30,7 @@ func (ac *Client) authenticateRsa(loginDetails *creds.LoginDetails) (string, err passcodeForm, passcodeActionURL, err := extractFormData(doc) if err != nil { - return "", errors.Wrap(err, "error extractign login data") + return "", errors.Wrap(err, "error extracting login data") } /** @@ -180,7 +180,7 @@ func (ac *Client) postRSAForm(rsaSubmitURL string, form url.Values) (*goquery.Do logger.WithField("status", res.StatusCode).WithField("rsaSubmitURL", rsaSubmitURL).WithField("res", dump.ResponseString(res)).Debug("POST") - data, err := ioutil.ReadAll(res.Body) + data, err := io.ReadAll(res.Body) if err != nil { return nil, errors.Wrap(err, "error retrieving body") } diff --git a/pkg/provider/adfs2/rsa_test.go b/pkg/provider/adfs2/rsa_test.go index 9d5c60f50..b9639507a 100644 --- a/pkg/provider/adfs2/rsa_test.go +++ b/pkg/provider/adfs2/rsa_test.go @@ -1,7 +1,6 @@ package adfs2 import ( - "io/ioutil" "net/http" "net/http/httptest" "net/url" @@ -17,7 +16,7 @@ import ( func TestClient_getLoginForm(t *testing.T) { - data, err := ioutil.ReadFile("example/loginpage.html") + data, err := os.ReadFile("example/loginpage.html") require.Nil(t, err) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -44,7 +43,7 @@ func TestClient_getLoginForm(t *testing.T) { func TestClient_postLoginForm(t *testing.T) { - data, err := ioutil.ReadFile("example/passcode.html") + data, err := os.ReadFile("example/passcode.html") require.Nil(t, err) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/provider/akamai/akamai.go b/pkg/provider/akamai/akamai.go index c3fd83401..6b42db627 100644 --- a/pkg/provider/akamai/akamai.go +++ b/pkg/provider/akamai/akamai.go @@ -4,7 +4,7 @@ import ( "bytes" "fmt" "html" - "io/ioutil" + "io" "log" "net/http" "net/url" @@ -159,7 +159,7 @@ func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) return samlAssertion, errors.Wrap(err, "error login to EAA IDP") } - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) if err != nil { return samlAssertion, errors.Wrap(err, "error retrieving body from response") } @@ -194,7 +194,7 @@ func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) return samlAssertion, errors.Wrap(err, "error while navigation request to EAA ") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return samlAssertion, errors.Wrap(err, "error retrieving response from navigate request") } @@ -232,7 +232,7 @@ func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) return samlAssertion, errors.Wrap(err, "error navigate request to EAA ") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return samlAssertion, errors.Wrap(err, "error retrieving body from response") } @@ -267,7 +267,7 @@ func verifyMfa(oc *Client, akamaiOrgHost string, loginDetails *creds.LoginDetail if err != nil { return errors.Wrap(err, "error mfa config request to EAA ") } - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) if err != nil { return errors.Wrap(err, "error retrieving mfa config request") } @@ -300,7 +300,7 @@ func verifyMfa(oc *Client, akamaiOrgHost string, loginDetails *creds.LoginDetail if err != nil { return errors.Wrap(err, "error mfa setting request to EAA ") } - mfaSettingData, err := ioutil.ReadAll(res.Body) + mfaSettingData, err := io.ReadAll(res.Body) if err != nil { return errors.Wrap(err, "error retrieving body from response") } @@ -376,7 +376,7 @@ func verifyMfa(oc *Client, akamaiOrgHost string, loginDetails *creds.LoginDetail return errors.Wrap(err, "error while sending MFA push code ") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return errors.Wrap(err, "error retrieving MFA push response ") } @@ -413,7 +413,7 @@ func verifyMfa(oc *Client, akamaiOrgHost string, loginDetails *creds.LoginDetail return errors.Wrap(err, "error verifying mfa to EAA ") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return errors.Wrap(err, "error retrieving mfa verify response") } @@ -523,7 +523,7 @@ func verifyMfa(oc *Client, akamaiOrgHost string, loginDetails *creds.LoginDetail return errors.Wrap(err, "error retrieving duo prompt request") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return errors.Wrap(err, "error retrieving duo prompt response") } @@ -555,7 +555,7 @@ func verifyMfa(oc *Client, akamaiOrgHost string, loginDetails *creds.LoginDetail return errors.Wrap(err, "error sending duo status request") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return errors.Wrap(err, "error retrieving duo status response") } @@ -584,7 +584,7 @@ func verifyMfa(oc *Client, akamaiOrgHost string, loginDetails *creds.LoginDetail return errors.Wrap(err, "error retrieving verify response") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return errors.Wrap(err, "error retrieving body from response") } @@ -619,7 +619,7 @@ func verifyMfa(oc *Client, akamaiOrgHost string, loginDetails *creds.LoginDetail return errors.Wrap(err, "error retrieving duo result response") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return errors.Wrap(err, "duoResultSubmit: error retrieving body from response") } @@ -655,7 +655,7 @@ func verifyMfa(oc *Client, akamaiOrgHost string, loginDetails *creds.LoginDetail return errors.Wrap(err, "error sending duo mfa request to EAA ") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return errors.Wrap(err, "error retrieving duo mfa response ") } diff --git a/pkg/provider/auth0/auth0.go b/pkg/provider/auth0/auth0.go index d67266c7e..4683214b2 100644 --- a/pkg/provider/auth0/auth0.go +++ b/pkg/provider/auth0/auth0.go @@ -6,7 +6,7 @@ import ( "encoding/json" "fmt" "html" - "io/ioutil" + "io" "net/http" "net/url" "regexp" @@ -86,7 +86,7 @@ type sessionInfo struct { csrf string } -//authCallbackRequest represents Auth0 authentication callback request +// authCallbackRequest represents Auth0 authentication callback request type authCallbackRequest struct { method string url string @@ -207,7 +207,7 @@ func (ac *Client) fetchSessionInfo(loginURL string) (*sessionInfo, error) { return nil, errors.Wrap(err, "error retrieving response") } - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.Wrap(err, "error retrieving response body") } @@ -249,7 +249,7 @@ func (ac *Client) getConnectionNames(connectionInfoURL string) ([]string, error) return nil, errors.Wrap(err, "error retrieving response") } - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.Wrap(err, "error retrieving body from response") } @@ -340,7 +340,7 @@ func (ac *Client) loginAuth0(loginDetails *creds.LoginDetails, ai *authInfo) (st return "", errors.Wrap(err, "error retrieving auth response") } - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { return "", errors.Wrap(err, "error retrieving body from response") } @@ -364,7 +364,7 @@ func (ac *Client) doAuthCallback(authCallback *authCallbackRequest, ai *authInfo return "", errors.Wrap(err, "error retrieving auth callback response") } - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { return "", errors.Wrap(err, "error retrieving body from response") } diff --git a/pkg/provider/authentik/authentik.go b/pkg/provider/authentik/authentik.go new file mode 100644 index 000000000..e7cb49d5c --- /dev/null +++ b/pkg/provider/authentik/authentik.go @@ -0,0 +1,283 @@ +package authentik + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + "github.com/versent/saml2aws/v2/pkg/cfg" + "github.com/versent/saml2aws/v2/pkg/creds" + "github.com/versent/saml2aws/v2/pkg/provider" +) + +// Client wrapper around authentik. +type Client struct { + provider.ValidateBase + + client *provider.HTTPClient +} + +var logger = logrus.WithField("provider", "authentik") + +// New create a new client +func New(idpAccount *cfg.IDPAccount) (*Client, error) { + tr := provider.NewDefaultTransport(idpAccount.SkipVerify) + + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) + if err != nil { + return nil, errors.Wrap(err, "error building http client") + } + + client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + return &Client{ + client: client, + }, nil +} + +// Authenticate Log into authentik and returns a SAML response +func (kc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) { + ctx := &authentikContext{ + loginDetails: loginDetails, + } + samlResponse, err := kc.auth(ctx) + if err != nil { + return "", errors.Wrap(err, "error retrieving saml response from idp") + } + + return samlResponse, err +} + +// auth Authentication +func (kc *Client) auth(ctx *authentikContext) (string, error) { + logger.Debug("[GET] ", ctx.loginDetails.URL) + res, err := kc.client.Get(ctx.loginDetails.URL) + if err != nil { + return "", errors.Wrap(err, "error retrieving initial url") + } + if res.StatusCode == http.StatusFound { + var location *url.URL + location, err = res.Location() + if err != nil { + return "", err + } + err = ctx.updateURL(location.String()) + if err != nil { + return "", err + } + + return kc.auth(ctx) + } + + requestURL := res.Request.URL + if len(res.Cookies()) > 0 { + baseURL := &url.URL{Scheme: requestURL.Scheme, Host: requestURL.Host, Path: "/"} + kc.client.Jar.SetCookies(baseURL, res.Cookies()) + } + + next, err := kc.processQuery(ctx) + if err != nil { + return "", err + } + if ctx.samlResponse != "" { + return ctx.samlResponse, nil + } + + err = ctx.updateURL(next) + if err != nil { + return "", err + } + return kc.auth(ctx) +} + +// processQuery Loop to get the authentik credentials +func (kc *Client) processQuery(ctx *authentikContext) (string, error) { + var shouldContinue bool + var next string + var err error + + next, err = queryNextURL(ctx.loginDetails.URL) + if err != nil { + return "", err + } + err = ctx.updateURL(next) + if err != nil { + return "", err + } + + for { + shouldContinue, next, err = kc.queryNext(ctx) + if err != nil { + return "", err + } + if next != "" { + err = ctx.updateURL(next) + if err != nil { + return "", err + } + } + if !shouldContinue { + break + } + } + + return next, nil +} + +// queryNext Do query and submit infos +func (kc *Client) queryNext(ctx *authentikContext) (bool, string, error) { + logger.Debug("[GET] ", ctx.loginDetails.URL) + res, err := kc.client.Get(ctx.loginDetails.URL) + if err != nil { + return false, "", err + } + if res.StatusCode == http.StatusFound { + next, err1 := res.Location() + if err1 != nil { + return false, "", err1 + } + err = ctx.updateURL(next.String()) + if err != nil { + return false, "", err + } + + return kc.queryNext(ctx) + } + var payload *authentikPayload + payload, err = parseResponsePayload(res) + if err != nil { + return false, "", err + } + if payload.isTypeRedirect() { + // login success if there is a redirect + logger.Debug("Login success, redirect to saml response") + return false, payload.RedirectTo, nil + } else if !payload.isTypeNative() { + return false, "", errors.New("Unknown type: " + payload.Type) + } + + if payload.isComponentStageAutosubmit() { + ctx.setSAMLResponse(payload.Attrs["SAMLResponse"]) + return false, "", nil + } + + next, err := kc.doPostQuery(ctx, payload) + return true, next, err +} + +// doPostQuery For all data setting operations +func (kc *Client) doPostQuery(ctx *authentikContext, payload *authentikPayload) (string, error) { + data, err := getLoginJSON(ctx.loginDetails, payload) + if err != nil { + return "", err + } + + logger.Debug("[POST]", ctx.loginDetails.URL) + res, err := kc.client.Post(ctx.loginDetails.URL, "application/json", bytes.NewReader(data)) + if err != nil { + return "", err + } + if res.StatusCode == http.StatusOK { + var payload *authentikPayload + payload, err = parseResponsePayload(res) + if err != nil { + return "", err + } + + var errMsg string + if len(payload.Errors) > 0 { + errMsg = prepareErrors(payload.Component, payload.Errors) + } else { + errMsg = "Unexpected" + } + + return "", errors.New(errMsg) + } + loc, err := res.Location() + return loc.String(), err +} + +// getLoginJSON Generate the login json +func getLoginJSON(loginDetails *creds.LoginDetails, payload *authentikPayload) ([]byte, error) { + component := payload.Component + m := map[string]string{ + "component": component, + } + switch component { + case "ak-stage-identification": + m["uid_field"] = loginDetails.Username + if payload.HasPassowrdField { + m["password"] = loginDetails.Password + } + case "ak-stage-password": + m["password"] = loginDetails.Password + default: + return []byte(""), errors.New("unknown component: " + component) + } + + return json.Marshal(m) +} + +// queryNextURL Get the next api url +func queryNextURL(u string) (string, error) { + next, err := url.Parse(u) + if err != nil { + return "", errors.New("Invalid url") + } + + result := strings.Split(next.Path, "/") + flow := result[len(result)-2] + return fmt.Sprintf("%s://%s/api/v3/flows/executor/%s/?query=%s", next.Scheme, next.Host, flow, url.QueryEscape(next.RawQuery)), nil +} + +// getFieldName Get name of component +func getFieldName(component string) (string, error) { + prefix := "ak-stage-" + if strings.Index(component, prefix) != 0 { + return "", errors.New("") + } + s := strings.Split(component, "ak-stage-") + return s[len(s)-1], nil +} + +// prepareErrors Transform errors to string +func prepareErrors(component string, errs map[string][]map[string]string) string { + field, err := getFieldName(component) + if err != nil { + return "Invalid component" + } + + key := "non_field_errors" + if field == "password" { + key = "password" + } + msgs := make([]string, 0, len(errs[key])) + for _, err := range errs[key] { + msgs = append(msgs, fmt.Sprintf("%s %s: %s", field, err["code"], err["string"])) + } + return strings.Join(msgs, "; ") +} + +// parseResponsePayload Parse response from authentik api +func parseResponsePayload(res *http.Response) (*authentikPayload, error) { + var payload authentikPayload + defer res.Body.Close() + b, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + err = json.Unmarshal(b, &payload) + if err != nil { + return nil, err + } + + return &payload, nil +} diff --git a/pkg/provider/authentik/authentik_test.go b/pkg/provider/authentik/authentik_test.go new file mode 100644 index 000000000..78c15c249 --- /dev/null +++ b/pkg/provider/authentik/authentik_test.go @@ -0,0 +1,320 @@ +package authentik + +import ( + "testing" + + "github.com/h2non/gock" + "github.com/stretchr/testify/assert" + + "github.com/versent/saml2aws/v2/pkg/cfg" + "github.com/versent/saml2aws/v2/pkg/creds" +) + +func Test_getLoginJSON(t *testing.T) { + assert := assert.New(t) + loginDetails := &creds.LoginDetails{ + Username: "user", + Password: "pwd", + URL: "https://127.0.0.1/sso/init", + } + payload := &authentikPayload{ + Component: "ak-stage-identification", + Type: "native", + } + b, err := getLoginJSON(loginDetails, payload) + assert.Nil(err) + assert.Equal(string(b), "{\"component\":\"ak-stage-identification\",\"uid_field\":\"user\"}") + + payload = &authentikPayload{ + Component: "ak-stage-password", + Type: "native", + } + b, err = getLoginJSON(loginDetails, payload) + assert.Nil(err) + assert.Equal(string(b), "{\"component\":\"ak-stage-password\",\"password\":\"pwd\"}") + + payload = &authentikPayload{ + Component: "ak-stage-test", + Type: "native", + } + _, err = getLoginJSON(loginDetails, payload) + assert.NotNil(err) +} + +func Test_queryNextURL(t *testing.T) { + assert := assert.New(t) + url, err := queryNextURL("https://127.0.0.1/if/flow/default-authentication-flow/?next=/application/saml/aws/sso/binding/init/") + assert.Nil(err) + assert.Equal(url, "https://127.0.0.1/api/v3/flows/executor/default-authentication-flow/?query=next%3D%2Fapplication%2Fsaml%2Faws%2Fsso%2Fbinding%2Finit%2F") +} + +func Test_getFieldName(t *testing.T) { + assert := assert.New(t) + var name string + var err error + name, err = getFieldName("ak-stage-identification") + assert.Nil(err) + assert.Equal(name, "identification") + + name, err = getFieldName("ak-stage-password") + assert.Nil(err) + assert.Equal(name, "password") + + name, err = getFieldName("ak-stage-") + assert.Nil(err) + assert.Equal(name, "") + + name, err = getFieldName("stage-password") + assert.NotNil(err) + assert.Equal(name, "") +} + +func Test_prepareErrors(t *testing.T) { + assert := assert.New(t) + var desc string + identification_errs := map[string][]map[string]string{ + "non_field_errors": { + { + "string": "Failed to authenticate.", + "code": "invalid", + }, + }, + } + desc = prepareErrors("ak-stage-identification", identification_errs) + assert.Equal(desc, "identification invalid: Failed to authenticate.") + + desc = prepareErrors("ak-stage-password", identification_errs) + assert.Equal(desc, "") + + passwordErrs := map[string][]map[string]string{ + "password": { + { + "string": "Failed to authenticate.", + "code": "invalid", + }, + }, + } + desc = prepareErrors("ak-stage-password", passwordErrs) + assert.Equal(desc, "password invalid: Failed to authenticate.") + + desc = prepareErrors("ak-stage-identification", passwordErrs) + assert.Equal(desc, "") +} + +// Test_authWithCombinedUsernamePassword Password only if username/email verified +func Test_authWithSeperatedUsernamePassword(t *testing.T) { + defer gock.Off() + samlResponse := "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaX" + gock.New("http://127.0.0.1"). + Get("/application/saml/aws/sso/binding/init"). + Reply(302). + SetHeader("Set-Cookie", "[authentik_session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiJ6cHI3NGdzMjNnOGNqbmF1bXNheGQ1dXVrc2VtZGZpNyIsImlzcyI6ImF1dGhlbnRpayIsInN1YiI6ImFub255bW91cyIsImF1dGhlbnRpY2F0ZWQiOmZhbHNlLCJhY3IiOiJnb2F1dGhlbnRpay5pby9jb3JlL2RlZmF1bHQifQ.zNiX4pk6G9ABeDip0PLs8-0irm2aQ_Arr_RgTxTGCQM; HttpOnly; Path=/; SameSite=None; Secure]"). + SetHeader("Location", "/flows/-/default/authentication/?next=/application/saml/aws/sso/binding/init/") + + gock.New("http://127.0.0.1"). + Get("/flows/-/default/authentication"). + Reply(302). + SetHeader("Location", "/if/flow/default-authentication-flow/?next=%2Fapplication%2Fsaml%2Faws%2Fsso%2Fbinding%2Finit%2F") + + gock.New("http://127.0.0.1"). + Get("/if/flow/default-authentication-flow"). + Reply(200). + BodyString("") + + gock.New("http://127.0.0.1"). + Get("/api/v3/flows/executor/default-authentication-flow"). + Reply(200). + JSON(map[string]interface{}{ + "type": "native", + "flow_info": map[string]interface{}{"title": "Welcome to authentik!", "background": "/static/dist/assets/images/flow_background.jpg", "cancel_url": "/flows/-/cancel/", "layout": "stacked"}, + "component": "ak-stage-identification", + "user_fields": []string{"username", "email"}, + "password_fields": false, + "application_pre": "aws", + "primary_action": "Log in", + "sources": []string{}, + "show_source_labels": false, + }) + + gock.New("http://127.0.0.1"). + Post("/api/v3/flows/executor/default-authentication-flow"). + Reply(302). + SetHeader("Location", "/api/v3/flows/executor/default-authentication-flow/?query=next%3D%252F") + + gock.New("http://127.0.0.1"). + Get("api/v3/flows/executor/default-authentication-flow"). + Reply(200). + JSON(map[string]interface{}{ + "type": "native", + "flow_info": map[string]interface{}{"title": "Welcome to authentik!", "background": "/static/dist/assets/images/flow_background.jpg", "cancel_url": "/flows/-/cancel/", "layout": "stacked"}, + "component": "ak-stage-password", + "pending_user": "user", + "pending_user_avatar": "https://secure.gravatar.com/avatar/0932141298741243?s=158&r=g", + }) + gock.New("http://127.0.0.1"). + Post("/api/v3/flows/executor/default-authentication-flow"). + Reply(302). + SetHeader("Location", "/api/v3/flows/executor/default-authentication-flow/?query=next%3D%252F") + + gock.New("http://127.0.0.1"). + Get("/api/v3/flows/executor/default-authentication-flow"). + Reply(302). + SetHeader("Location", "/api/v3/flows/executor/default-authentication-flow/?query=next%3D%252F") + + gock.New("http://127.0.0.1"). + Get("/api/v3/flows/executor/default-authentication-flow"). + Reply(200). + JSON(map[string]interface{}{ + "type": "redirect", + "to": "http://127.0.0.1/application/saml/aws/sso/binding/init", + }) + + gock.New("http://127.0.0.1"). + Get("/application/saml/aws/sso/binding/init"). + Reply(302). + SetHeader("Location", "/if/flow/default-provider-authorization-implicit-consent/") + + gock.New("http://127.0.0.1"). + Get("/if/flow/default-provider-authorization-implicit-consent/"). + Reply(200) + + gock.New("http://127.0.0.1"). + Get("/api/v3/flows"). + Reply(200). + JSON(map[string]interface{}{ + "type": "native", + "flow_info": map[string]interface{}{ + "title": "Redirecting to aws", + "background": "/static/dist/assets/images/flow_background.jpg", + "cancel_url": "/flows/-/cancel/", + "layout": "stacked", + }, + "component": "ak-stage-autosubmit", + "url": "https://signin.amazonaws.com/saml", + "attrs": map[string]interface{}{ + "ACSUrl": "https://signin.amazonaws.com/saml", + "SAMLResponse": samlResponse, + }, + }) + client, _ := New(&cfg.IDPAccount{}) + loginDetails := &creds.LoginDetails{ + Username: "user", + Password: "pwd", + URL: "http://127.0.0.1/application/saml/aws/sso/binding/init", + } + gock.InterceptClient(&client.client.Client) + result, err := client.Authenticate(loginDetails) + + assert := assert.New(t) + assert.Nil(err) + assert.Equal(result, samlResponse) +} + +// Test_authWithCombinedUsernamePassword Username/email and password in one page +func Test_authWithCombinedUsernamePassword(t *testing.T) { + defer gock.Off() + samlResponse := "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaX" + gock.New("http://127.0.0.1"). + Get("/application/saml/aws/sso/binding/init"). + Reply(302). + SetHeader("Set-Cookie", "[authentik_session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiJ6cHI3NGdzMjNnOGNqbmF1bXNheGQ1dXVrc2VtZGZpNyIsImlzcyI6ImF1dGhlbnRpayIsInN1YiI6ImFub255bW91cyIsImF1dGhlbnRpY2F0ZWQiOmZhbHNlLCJhY3IiOiJnb2F1dGhlbnRpay5pby9jb3JlL2RlZmF1bHQifQ.zNiX4pk6G9ABeDip0PLs8-0irm2aQ_Arr_RgTxTGCQM; HttpOnly; Path=/; SameSite=None; Secure]"). + SetHeader("Location", "/flows/-/default/authentication/?next=/application/saml/aws/sso/binding/init/") + + gock.New("http://127.0.0.1"). + Get("/flows/-/default/authentication"). + Reply(302). + SetHeader("Location", "/if/flow/default-authentication-flow/?next=%2Fapplication%2Fsaml%2Faws%2Fsso%2Fbinding%2Finit%2F") + + gock.New("http://127.0.0.1"). + Get("/if/flow/default-authentication-flow"). + Reply(200). + BodyString("") + + gock.New("http://127.0.0.1"). + Get("/api/v3/flows/executor/default-authentication-flow"). + Reply(200). + JSON(map[string]interface{}{ + "type": "native", + "flow_info": map[string]interface{}{"title": "Welcome to authentik!", "background": "/static/dist/assets/images/flow_background.jpg", "cancel_url": "/flows/-/cancel/", "layout": "stacked"}, + "component": "ak-stage-identification", + "user_fields": []string{"username", "email"}, + "password_fields": true, + "application_pre": "aws", + "primary_action": "Log in", + "sources": []string{}, + "show_source_labels": false, + }) + + gock.New("http://127.0.0.1"). + Post("/api/v3/flows/executor/default-authentication-flow"). + Reply(302). + SetHeader("Location", "/api/v3/flows/executor/default-authentication-flow/?query=next%3D%252F") + + gock.New("http://127.0.0.1"). + Get("api/v3/flows/executor/default-authentication-flow"). + Reply(200). + JSON(map[string]interface{}{ + "type": "native", + "flow_info": map[string]interface{}{"title": "Welcome to authentik!", "background": "/static/dist/assets/images/flow_background.jpg", "cancel_url": "/flows/-/cancel/", "layout": "stacked"}, + "component": "ak-stage-password", + "pending_user": "user", + "pending_user_avatar": "https://secure.gravatar.com/avatar/0932141298741243?s=158&r=g", + }) + gock.New("http://127.0.0.1"). + Post("/api/v3/flows/executor/default-authentication-flow"). + Reply(302). + SetHeader("Location", "/api/v3/flows/executor/default-authentication-flow/?query=next%3D%252F") + + gock.New("http://127.0.0.1"). + Get("/api/v3/flows/executor/default-authentication-flow"). + Reply(302). + SetHeader("Location", "/api/v3/flows/executor/default-authentication-flow/?query=next%3D%252F") + + gock.New("http://127.0.0.1"). + Get("/api/v3/flows/executor/default-authentication-flow"). + Reply(200). + JSON(map[string]interface{}{ + "type": "redirect", + "to": "http://127.0.0.1/application/saml/aws/sso/binding/init", + }) + + gock.New("http://127.0.0.1"). + Get("/application/saml/aws/sso/binding/init"). + Reply(302). + SetHeader("Location", "/if/flow/default-provider-authorization-implicit-consent/") + + gock.New("http://127.0.0.1"). + Get("/if/flow/default-provider-authorization-implicit-consent/"). + Reply(200) + + gock.New("http://127.0.0.1"). + Get("/api/v3/flows"). + Reply(200). + JSON(map[string]interface{}{ + "type": "native", + "flow_info": map[string]interface{}{ + "title": "Redirecting to aws", + "background": "/static/dist/assets/images/flow_background.jpg", + "cancel_url": "/flows/-/cancel/", + "layout": "stacked", + }, + "component": "ak-stage-autosubmit", + "url": "https://signin.amazonaws.com/saml", + "attrs": map[string]interface{}{ + "ACSUrl": "https://signin.amazonaws.com/saml", + "SAMLResponse": samlResponse, + }, + }) + client, _ := New(&cfg.IDPAccount{}) + loginDetails := &creds.LoginDetails{ + Username: "user", + Password: "pwd", + URL: "http://127.0.0.1/application/saml/aws/sso/binding/init", + } + gock.InterceptClient(&client.client.Client) + result, err := client.Authenticate(loginDetails) + + assert := assert.New(t) + assert.Nil(err) + assert.Equal(result, samlResponse) +} diff --git a/pkg/provider/authentik/model.go b/pkg/provider/authentik/model.go new file mode 100644 index 000000000..1bffe7d9f --- /dev/null +++ b/pkg/provider/authentik/model.go @@ -0,0 +1,54 @@ +package authentik + +import ( + "fmt" + "net/url" + "strings" + + "github.com/pkg/errors" + + "github.com/versent/saml2aws/v2/pkg/creds" +) + +type authentikContext struct { + loginDetails *creds.LoginDetails + samlResponse string +} + +type authentikPayload struct { + Attrs map[string]string + Component string + Type string + HasPassowrdField bool `json:"password_fields"` + RedirectTo string `json:"to"` + Errors map[string][]map[string]string `json:"response_errors"` +} + +func (ctx *authentikContext) updateURL(s string) error { + if strings.Index(s, "/") == 0 { + u, err := url.Parse(ctx.loginDetails.URL) + if err != nil { + return errors.New("Invalid url") + } + s = fmt.Sprintf("%s://%s%s", u.Scheme, u.Host, s) + } + + ctx.loginDetails.URL = s + return nil +} + +func (ctx *authentikContext) setSAMLResponse(val string) { + ctx.samlResponse = val +} + +func (payload *authentikPayload) isTypeNative() bool { + return payload.Type == "native" +} + +func (payload *authentikPayload) isTypeRedirect() bool { + return payload.Type == "redirect" +} + +func (payload *authentikPayload) isComponentStageAutosubmit() bool { + return payload.Component == "ak-stage-autosubmit" +} diff --git a/pkg/provider/authentik/model_test.go b/pkg/provider/authentik/model_test.go new file mode 100644 index 000000000..62df451fd --- /dev/null +++ b/pkg/provider/authentik/model_test.go @@ -0,0 +1,26 @@ +package authentik + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/versent/saml2aws/v2/pkg/creds" +) + +func Test_updateURL(t *testing.T) { + assert := assert.New(t) + ctx := &authentikContext{ + loginDetails: &creds.LoginDetails{ + Username: "user", + Password: "pwd", + URL: "https://127.0.0.1/sso/init", + }, + } + err := ctx.updateURL("/query?next=/login") + assert.Nil(err) + assert.Equal(ctx.loginDetails.URL, "https://127.0.0.1/query?next=/login") + + err = ctx.updateURL("https://127.0.0.1:8888/sso/aws") + assert.Nil(err) + assert.Equal(ctx.loginDetails.URL, "https://127.0.0.1:8888/sso/aws") +} diff --git a/pkg/provider/browser/browser.go b/pkg/provider/browser/browser.go index d2947bfee..faa605368 100644 --- a/pkg/provider/browser/browser.go +++ b/pkg/provider/browser/browser.go @@ -2,9 +2,12 @@ package browser import ( "errors" + "fmt" "net/url" + "regexp" + "strings" - "github.com/mxschmitt/playwright-go" + "github.com/playwright-community/playwright-go" "github.com/sirupsen/logrus" "github.com/versent/saml2aws/v2/pkg/cfg" "github.com/versent/saml2aws/v2/pkg/creds" @@ -12,31 +15,94 @@ import ( var logger = logrus.WithField("provider", "browser") +const DEFAULT_TIMEOUT float64 = 300000 + // Client client for browser based Identity Provider type Client struct { + BrowserType string + BrowserExecutablePath string + Headless bool + // Setup alternative directory to download playwright browsers to + BrowserDriverDir string + Timeout int } // New create new browser based client func New(idpAccount *cfg.IDPAccount) (*Client, error) { - return &Client{}, nil + return &Client{ + Headless: idpAccount.Headless, + BrowserDriverDir: idpAccount.BrowserDriverDir, + BrowserType: strings.ToLower(idpAccount.BrowserType), + BrowserExecutablePath: idpAccount.BrowserExecutablePath, + Timeout: idpAccount.Timeout, + }, nil } +// contains checks if a string is present in a slice +func contains(s []string, str string) bool { + for _, v := range s { + if v == str { + return true + } + } + + return false +} func (cl *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) { + runOptions := playwright.RunOptions{} + if cl.BrowserDriverDir != "" { + runOptions.DriverDirectory = cl.BrowserDriverDir + } + + // Optionally download browser drivers if specified + if loginDetails.DownloadBrowser { + err := playwright.Install(&runOptions) + if err != nil { + return "", err + } + } - pw, err := playwright.Run() + pw, err := playwright.Run(&runOptions) if err != nil { return "", err } // TODO: provide some overrides for this window launchOptions := playwright.BrowserTypeLaunchOptions{ - Headless: playwright.Bool(false), + Headless: playwright.Bool(cl.Headless), + } + + validBrowserTypes := []string{"chromium", "firefox", "webkit", "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", "msedge-canary"} + if len(cl.BrowserType) > 0 && !contains(validBrowserTypes, cl.BrowserType) { + return "", fmt.Errorf("invalid browser-type: '%s', only %s are allowed", cl.BrowserType, validBrowserTypes) + } + + if cl.BrowserType != "" { + logger.Info(fmt.Sprintf("Setting browser type: %s", cl.BrowserType)) + launchOptions.Channel = playwright.String(cl.BrowserType) + } + + // Default browser is Chromium as it is widely supported for Identity providers, + // It can also be set to the other playwright browsers: Firefox and WebKit + browserType := pw.Chromium + if cl.BrowserType == "firefox" { + browserType = pw.Firefox + } else if cl.BrowserType == "webkit" { + browserType = pw.WebKit + } + + // You can set the path to a browser executable to run instead of the playwright-go bundled one. If `executablePath` + // is a relative path, then it is resolved relative to the current working directory. + // Note that Playwright only works with the bundled Chromium, Firefox or WebKit, use at your own risk. see: + if len(cl.BrowserExecutablePath) > 0 { + logger.Info(fmt.Sprintf("Setting browser executable path: %s", cl.BrowserExecutablePath)) + launchOptions.ExecutablePath = &cl.BrowserExecutablePath } - // currently using Chromium as it is widely supported for Identity providers + // currently using the main browsers supported by Playwright: Chromium, Firefox or Webkit // // this is a sandboxed browser window so password managers and addons are separate - browser, err := pw.Chromium.Launch(launchOptions) + browser, err := browserType.Launch(launchOptions) if err != nil { return "", err } @@ -46,35 +112,56 @@ func (cl *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) return "", err } + defer func() { + logger.Info("clean up browser") + if err := browser.Close(); err != nil { + logger.Info("Error when closing browser", err) + } + if err := pw.Stop(); err != nil { + logger.Info("Error when stopping pm", err) + } + }() + + return getSAMLResponse(page, loginDetails, cl) +} + +var getSAMLResponse = func(page playwright.Page, loginDetails *creds.LoginDetails, client *Client) (string, error) { logger.WithField("URL", loginDetails.URL).Info("opening browser") if _, err := page.Goto(loginDetails.URL); err != nil { return "", err } - r := page.WaitForRequest("https://signin.aws.amazon.com/saml") - data, err := r.PostData() + // https://docs.aws.amazon.com/general/latest/gr/signin-service.html + // https://docs.amazonaws.cn/en_us/aws/latest/userguide/endpoints-Ningxia.html + // https://docs.amazonaws.cn/en_us/aws/latest/userguide/endpoints-Beijing.html + signin_re, err := signinRegex() if err != nil { return "", err } - values, err := url.ParseQuery(data) + logger.Info("waiting ...") + r, _ := page.ExpectRequest(signin_re, nil, client.expectRequestTimeout()) + data, err := r.PostData() if err != nil { return "", err } - logger.Info("clean up browser") - - if err = browser.Close(); err != nil { - return "", err - } - if err = pw.Stop(); err != nil { + values, err := url.ParseQuery(data) + if err != nil { return "", err } return values.Get("SAMLResponse"), nil } +func signinRegex() (*regexp.Regexp, error) { + // https://docs.aws.amazon.com/general/latest/gr/signin-service.html + // https://docs.amazonaws.cn/en_us/aws/latest/userguide/endpoints-Ningxia.html + // https://docs.amazonaws.cn/en_us/aws/latest/userguide/endpoints-Beijing.html + return regexp.Compile(`https:\/\/((.*\.)?signin\.(aws\.amazon\.com|amazonaws-us-gov\.com|amazonaws\.cn))\/saml`) +} + func (cl *Client) Validate(loginDetails *creds.LoginDetails) error { if loginDetails.URL == "" { @@ -83,3 +170,11 @@ func (cl *Client) Validate(loginDetails *creds.LoginDetails) error { return nil } + +func (cl *Client) expectRequestTimeout() playwright.PageExpectRequestOptions { + timeout := float64(cl.Timeout) + if timeout < 30000 { + timeout = DEFAULT_TIMEOUT + } + return playwright.PageExpectRequestOptions{Timeout: &timeout} +} diff --git a/pkg/provider/browser/browser_test.go b/pkg/provider/browser/browser_test.go new file mode 100644 index 000000000..d74cc0567 --- /dev/null +++ b/pkg/provider/browser/browser_test.go @@ -0,0 +1,199 @@ +package browser + +import ( + "net/url" + "testing" + + "github.com/playwright-community/playwright-go" + "github.com/stretchr/testify/assert" + "github.com/versent/saml2aws/v2/mocks" + "github.com/versent/saml2aws/v2/pkg/cfg" + "github.com/versent/saml2aws/v2/pkg/creds" +) + +var response = ` + + http://idp.example.com/metadata.php + + + + + hKoNWraWb2YHo6B8ZFI5wOj8C0zdeYnQpaIE14vqe67LCy9e4Y+q7lMTRa7gNa6WZMJbkj1aQ/omsQLRVMkknKFoGD244J0Or9Ma8aXoEkQuRFyw90G4SkH5KuE6ZjUMJkMZN6xDYC+CozYiD6Pfchth/Ks64dNLJ2REau1dV/0= + + wbjgmIwWtVqv/doV7LTdN7pKkQTQWxpZz1jme5KeB0PidGQLJrHsvNYZpWWixh9b8InsOLmshPJqt5M3CMXqGItxsaQ8rAZim351eYRoDMWPUWydPdZIwoqdhcbrpxmVZsJ0RIjtSPgKBkGFjLL28KTS+JmvMDUdpW8qi+ssr+8661gVuvB5BEp2g+hblb7FGtDJxpYoOumh9zgEqeNiUQ0PcwuRbMq6I0LTWtpA2USlM8Lh+TqfQS/2yL/HioZSby6Uvql+7pOUqwgEzOyeFWepkPtawcasgF6tXIT0b/U6ro9xg4x37+znYjiQCdSMmuiE6ipuBjWc9Zj3sYITVIpOqUTmPoZQ+Jg143/Mjj7Lx4EAnYwJzF6rgrgN20Ep+LCWJ8uPfirK7xz3mq3I5AC1Mv04lASX++vlzFJDXpX2cjeMTQfKQRdzBCEUxn87wP7IeKupOmuBRCIN9kx/cTHkjW8LW2cWQ4xlWKYE0FrvT7k853U0mtz5DlCo3UsQdgp4OKPdwzHDK+/hOjMBwdIeRSZZhLlkVK7v1GesJBrPX5u9dsXjW4jrexeRKntXJ4LBA+bGFjiBKNZci2j85ZC+tQ+TFmBAF/KFyM3U+AU/YzfbGvo/owKL46DIpw8XPeGBBN7mYbp7F7C3RpS67ex4d93FhUo1+c8nFKpfK9crLuJCg3zW/YjVPfhDb7l+i+ckQ2CyIQOffAtrJK1bgq1g7SokgtVbfIXRkhXCB/eGuCDmELuOaPkBpvtHrOy5R2OA/UaB3tkT9q2scWRO94H20Xz5+hUwMnbnnYMXBdhbIGSkisYnL+0CjYyh5NpvpFxczZI4i5N4EFwGGqbBRokVa7fZCyrQRcCnM1UX4F+NGSwebEFoJRcJibCU8pHXEj176TT+ZwJw6h23WZmdFaiZl7x80tLISYPUzkc3phG6ytWzCtY5LokIzi4IUQd8lnqa64tnWljx45BJq7te16Q4TAi9sUy2PeB/Cug3nThTYJOM2VRBhh5BZFZEu/NuCMRoL+/t8GWXgmlHPyXX7hC+EG3P6RaAUmXeOt3bcEY3EbR1lz/+20/y32YBh3nwWWJqwoCwqdymDHN8VqVbJqRkVs5vlVuhJFROR1oh1M3RE7KPtQsxOUkihnieftQqmolq2fNOA/1HRcOmczisgAt02cf7wkQEHtXUpXloTrWUu44eZo++HAt4bPehLE0Y4ojeu174WWjVXEf2o8JGjTj7JVKrKGVwDwHfbbZJhezyYtmApwP70ZJjXxjGmGzW8OoPDS2pBBE+FGyI2drwWqZ5j5EVyHXRqnMcDhS1Sd/WQ53lreedRO+QcvGDUE1mK8j3pYKWO20prGjlYzIEQXJGmxfYVi5IF4hNVNtQ16SiQaHuGwcumkQIxIEbDBFaynj2SBJmwuK/DF/wglKlIassIqgWrFKkk2S04SdUdv7/PFUxbc5u1x/oaluRcDWUnClAPnK52RdxEFr1l0ht6deyEQWokylCNpnBAntmV8muMCdTRYz2qDJC2JBuab3RfCqsXhHzmyVLEHDz7S/xClZVZx01CxqbFTv0x08wWTrDHbtOmBsvAhlfZzTbEyu8SHOb2oMYwilzSHYNndJKV8bctcNUm/54sKzQkkU2xmz8sbrpAofFHEvWNmQ3qRc6XIuU4OJoKgNHnJtwx7/En0MlM/cqVs7yYxKJ3VBtafHJM9h0cs9ZLD1qw7v0nyTPCY13hyTM9N20Bh39SN1gTPg+kZZwhpGRBzi4/LiR96QokFsxCgVOeIeOWuvG/iG53BM6unybZeenKv6OD+w/f2zLhP5ATK9YMQCGh15PHwLUkDJP05dKPwtinU/ywiBdHU3Pr5ZyZN5s/pp5VWh2LI032demuWIWSXirQXc++amCXX1xSKgl4wr/qGBjqg/Qsvo/e2hKwhkWZ+WrkrZ1fvwv27neTH1pVi0FHtNzY9Tp4lJLxmwB126MMmhoQXCF+f/4HOTMa3uJh/htwTOE7eN5yzAEZGX6RyiO8tdlY3B6LBPFEnou9Ha0Nyemw1hhvkdCTydcUGXQ0wEyU+4Sp8YUZAV4x0JFB/0WeNaEDmugCFajknjNDN2QYMMNaATM3jYuVD1zcoYLQKYb9RZbAznXTUGqQFb6RtrSStERCsEKm1/ovf9KiuqYb1ItGOXFQbpcQRXguWHpF5c39ncKmyoIqPIbjCS5DWaNkq+rdUMv5K8KPurY4bpFFli9ytDQD7PFZ6uxeWH9lu6HzS6uzvuSGvx8VQaGyjP5lJZtrFxnj6K4Ev6duvMafJnrzhIUpl6FimmW3JOjTQIKobyW/hhQHxDVf1zDq0m/UEvXoUVVMiFg9QELCd2pNpgGcc2aSeIsc5vMdnMMBcTfLdKs7FAYMFuKh2e5nJdhWUam97HbtOnzsT04B+EsRNbLyqgf+x54yN5/xxtg7N+cUQ8IZcOwk3+kGzmaq681wFQ6PnBNFhUOFKvAhC20EPXyANtTGFr6LvvxPfUmnXkTJE5hLqkgy4qZDgJrARfPOPe1mcwu/m8ttrxcEYso95nBMZblI0UC7bp7QT2xCdGvbi8Zwi0OVbshlVx6PDbDll1f0rEgxAoYUSEF3zrjW/vRk8njBKAt/vmmI0/aDHYZlnVJG4AbVQ+T4UAWCVgJJIuCRN4Owh4m92a8p4cgqB+3PKIWceyS0je4RfOjEpRql+VJrPx58qKJuXXW2aBWHay7QSsaPuseCuP3DKaUKYiLLl/Q7hCIhgImte5l7RKl2rlDE8i0A7/p7zT6rTP3+1jbEIeYyw2T33mq15hGKt/acUjsS++8lfLURcPU1vNpwg75Y0ry+fl1vGwBtwkqZRD8ZoBhL7iyxOwbL8iD97eO9tgvYDYhrJjjpfiuke4ReUu261YabAaS858VxZotuLlTT/g= + + + + +` + +func TestValidate(t *testing.T) { + currentSAMLResponse := getSAMLResponse + defer func() { + getSAMLResponse = currentSAMLResponse + }() + getSAMLResponse = fakeSAMLResponse + account := &cfg.IDPAccount{ + Headless: true, + } + client, err := New(account) + assert.Nil(t, err) + loginDetails := &creds.LoginDetails{ + URL: "https://google.com/", + DownloadBrowser: true, + } + resp, err := client.Authenticate(loginDetails) + assert.Nil(t, err) + assert.Equal(t, resp, response) +} + +func TestInvalidBrowserType(t *testing.T) { + currentSAMLResponse := getSAMLResponse + defer func() { + getSAMLResponse = currentSAMLResponse + }() + getSAMLResponse = fakeSAMLResponse + account := &cfg.IDPAccount{ + BrowserType: "invalid", + } + client, err := New(account) + assert.Nil(t, err) + loginDetails := &creds.LoginDetails{ + URL: "https://google.com/", + DownloadBrowser: true, + } + _, err = client.Authenticate(loginDetails) + assert.Error(t, err) + assert.ErrorContains(t, err, "invalid browser-type: 'invalid', only [chromium firefox webkit chrome chrome-beta chrome-dev chrome-canary msedge msedge-beta msedge-dev msedge-canary] are allowed") +} + +func TestInvalidBrowserExecutablePath(t *testing.T) { + currentSAMLResponse := getSAMLResponse + defer func() { + getSAMLResponse = currentSAMLResponse + }() + getSAMLResponse = fakeSAMLResponse + account := &cfg.IDPAccount{ + BrowserExecutablePath: "FAKEPATH", + } + client, err := New(account) + assert.Nil(t, err) + loginDetails := &creds.LoginDetails{ + URL: "https://google.com/", + } + _, err = client.Authenticate(loginDetails) + assert.Error(t, err) + assert.ErrorContains(t, err, "Failed to launch chromium because executable doesn't exist at FAKEPATH") +} + +// Test that if download directory does not have browsers, it fails with expected error message +func TestNoBrowserDriverFail(t *testing.T) { + account := &cfg.IDPAccount{ + Headless: true, + BrowserDriverDir: t.TempDir(), // set up a directory we know won't have drivers + } + loginDetails := &creds.LoginDetails{ + URL: "https://google.com/", + } + client, _ := New(account) + _, err := client.Authenticate(loginDetails) + assert.Error(t, err) + assert.ErrorContains(t, err, "could not start driver") +} + +func fakeSAMLResponse(page playwright.Page, loginDetails *creds.LoginDetails, client *Client) (string, error) { + return response, nil +} + +func TestSigninRegex1(t *testing.T) { + regex, err := signinRegex() + assert.Nil(t, err) + match := regex.MatchString("https://signin.aws.amazon.com/saml") + assert.True(t, match) +} + +func TestSigninRegexFail(t *testing.T) { + regex, err := signinRegex() + assert.Nil(t, err) + match := regex.MatchString("https://google.com/") + assert.False(t, match) +} + +func TestGetSAMLResponse(t *testing.T) { + samlp := ` + + http://idp.example.com/metadata.php + + + + + hKoNWraWb2YHo6B8ZFI5wOj8C0zdeYnQpaIE14vqe67LCy9e4Y+q7lMTRa7gNa6WZMJbkj1aQ/omsQLRVMkknKFoGD244J0Or9Ma8aXoEkQuRFyw90G4SkH5KuE6ZjUMJkMZN6xDYC+CozYiD6Pfchth/Ks64dNLJ2REau1dV/0= + + wbjgmIwWtVqv/doV7LTdN7pKkQTQWxpZz1jme5KeB0PidGQLJrHsvNYZpWWixh9b8InsOLmshPJqt5M3CMXqGItxsaQ8rAZim351eYRoDMWPUWydPdZIwoqdhcbrpxmVZsJ0RIjtSPgKBkGFjLL28KTS+JmvMDUdpW8qi+ssr+8661gVuvB5BEp2g+hblb7FGtDJxpYoOumh9zgEqeNiUQ0PcwuRbMq6I0LTWtpA2USlM8Lh+TqfQS/2yL/HioZSby6Uvql+7pOUqwgEzOyeFWepkPtawcasgF6tXIT0b/U6ro9xg4x37+znYjiQCdSMmuiE6ipuBjWc9Zj3sYITVIpOqUTmPoZQ+Jg143/Mjj7Lx4EAnYwJzF6rgrgN20Ep+LCWJ8uPfirK7xz3mq3I5AC1Mv04lASX++vlzFJDXpX2cjeMTQfKQRdzBCEUxn87wP7IeKupOmuBRCIN9kx/cTHkjW8LW2cWQ4xlWKYE0FrvT7k853U0mtz5DlCo3UsQdgp4OKPdwzHDK+/hOjMBwdIeRSZZhLlkVK7v1GesJBrPX5u9dsXjW4jrexeRKntXJ4LBA+bGFjiBKNZci2j85ZC+tQ+TFmBAF/KFyM3U+AU/YzfbGvo/owKL46DIpw8XPeGBBN7mYbp7F7C3RpS67ex4d93FhUo1+c8nFKpfK9crLuJCg3zW/YjVPfhDb7l+i+ckQ2CyIQOffAtrJK1bgq1g7SokgtVbfIXRkhXCB/eGuCDmELuOaPkBpvtHrOy5R2OA/UaB3tkT9q2scWRO94H20Xz5+hUwMnbnnYMXBdhbIGSkisYnL+0CjYyh5NpvpFxczZI4i5N4EFwGGqbBRokVa7fZCyrQRcCnM1UX4F+NGSwebEFoJRcJibCU8pHXEj176TT+ZwJw6h23WZmdFaiZl7x80tLISYPUzkc3phG6ytWzCtY5LokIzi4IUQd8lnqa64tnWljx45BJq7te16Q4TAi9sUy2PeB/Cug3nThTYJOM2VRBhh5BZFZEu/NuCMRoL+/t8GWXgmlHPyXX7hC+EG3P6RaAUmXeOt3bcEY3EbR1lz/+20/y32YBh3nwWWJqwoCwqdymDHN8VqVbJqRkVs5vlVuhJFROR1oh1M3RE7KPtQsxOUkihnieftQqmolq2fNOA/1HRcOmczisgAt02cf7wkQEHtXUpXloTrWUu44eZo++HAt4bPehLE0Y4ojeu174WWjVXEf2o8JGjTj7JVKrKGVwDwHfbbZJhezyYtmApwP70ZJjXxjGmGzW8OoPDS2pBBE+FGyI2drwWqZ5j5EVyHXRqnMcDhS1Sd/WQ53lreedRO+QcvGDUE1mK8j3pYKWO20prGjlYzIEQXJGmxfYVi5IF4hNVNtQ16SiQaHuGwcumkQIxIEbDBFaynj2SBJmwuK/DF/wglKlIassIqgWrFKkk2S04SdUdv7/PFUxbc5u1x/oaluRcDWUnClAPnK52RdxEFr1l0ht6deyEQWokylCNpnBAntmV8muMCdTRYz2qDJC2JBuab3RfCqsXhHzmyVLEHDz7S/xClZVZx01CxqbFTv0x08wWTrDHbtOmBsvAhlfZzTbEyu8SHOb2oMYwilzSHYNndJKV8bctcNUm/54sKzQkkU2xmz8sbrpAofFHEvWNmQ3qRc6XIuU4OJoKgNHnJtwx7/En0MlM/cqVs7yYxKJ3VBtafHJM9h0cs9ZLD1qw7v0nyTPCY13hyTM9N20Bh39SN1gTPg+kZZwhpGRBzi4/LiR96QokFsxCgVOeIeOWuvG/iG53BM6unybZeenKv6OD+w/f2zLhP5ATK9YMQCGh15PHwLUkDJP05dKPwtinU/ywiBdHU3Pr5ZyZN5s/pp5VWh2LI032demuWIWSXirQXc++amCXX1xSKgl4wr/qGBjqg/Qsvo/e2hKwhkWZ+WrkrZ1fvwv27neTH1pVi0FHtNzY9Tp4lJLxmwB126MMmhoQXCF+f/4HOTMa3uJh/htwTOE7eN5yzAEZGX6RyiO8tdlY3B6LBPFEnou9Ha0Nyemw1hhvkdCTydcUGXQ0wEyU+4Sp8YUZAV4x0JFB/0WeNaEDmugCFajknjNDN2QYMMNaATM3jYuVD1zcoYLQKYb9RZbAznXTUGqQFb6RtrSStERCsEKm1/ovf9KiuqYb1ItGOXFQbpcQRXguWHpF5c39ncKmyoIqPIbjCS5DWaNkq+rdUMv5K8KPurY4bpFFli9ytDQD7PFZ6uxeWH9lu6HzS6uzvuSGvx8VQaGyjP5lJZtrFxnj6K4Ev6duvMafJnrzhIUpl6FimmW3JOjTQIKobyW/hhQHxDVf1zDq0m/UEvXoUVVMiFg9QELCd2pNpgGcc2aSeIsc5vMdnMMBcTfLdKs7FAYMFuKh2e5nJdhWUam97HbtOnzsT04B+EsRNbLyqgf+x54yN5/xxtg7N+cUQ8IZcOwk3+kGzmaq681wFQ6PnBNFhUOFKvAhC20EPXyANtTGFr6LvvxPfUmnXkTJE5hLqkgy4qZDgJrARfPOPe1mcwu/m8ttrxcEYso95nBMZblI0UC7bp7QT2xCdGvbi8Zwi0OVbshlVx6PDbDll1f0rEgxAoYUSEF3zrjW/vRk8njBKAt/vmmI0/aDHYZlnVJG4AbVQ+T4UAWCVgJJIuCRN4Owh4m92a8p4cgqB+3PKIWceyS0je4RfOjEpRql+VJrPx58qKJuXXW2aBWHay7QSsaPuseCuP3DKaUKYiLLl/Q7hCIhgImte5l7RKl2rlDE8i0A7/p7zT6rTP3+1jbEIeYyw2T33mq15hGKt/acUjsS++8lfLURcPU1vNpwg75Y0ry+fl1vGwBtwkqZRD8ZoBhL7iyxOwbL8iD97eO9tgvYDYhrJjjpfiuke4ReUu261YabAaS858VxZotuLlTT/g= + + + + +` + + idpAccount := cfg.IDPAccount{ + Headless: true, + Timeout: 100000, + } + + client, err := New(&idpAccount) + assert.Nil(t, err) + params := url.Values{} + params.Add("foo1", "bar1") + params.Add("SAMLResponse", samlp) + params.Add("foo2", "bar2") + url := "https://google.com/" + page := &mocks.Page{} + resp := &mocks.Response{} + req := &mocks.Request{} + regex, err := signinRegex() + assert.Nil(t, err) + page.Mock.On("Goto", url).Return(resp, nil) + page.Mock.On("ExpectRequest", regex, client.expectRequestTimeout()).Return(req) + req.Mock.On("PostData").Return(params.Encode(), nil) + // loginDetails := &creds.LoginDetails{ + // URL: url, + //} + // samlResp, err := getSAMLResponse(page, loginDetails, client) + // assert.Nil(t, err) + // assert.Equal(t, samlp, samlResp) +} + +func TestExpectRequestOptions(t *testing.T) { + timeout := float64(100000) + idpAccount := cfg.IDPAccount{ + Headless: true, + Timeout: int(timeout), + } + + client, err := New(&idpAccount) + assert.Nil(t, err) + + options := client.expectRequestTimeout() + if *options.Timeout != timeout { + t.Errorf("Unexpected value for timeout [%.0f]: expected [%.0f]", *options.Timeout, timeout) + } +} + +func TestExpectRequestOptionsDefaultTimeout(t *testing.T) { + idpAccount := cfg.IDPAccount{ + Headless: true, + Timeout: 1000, + } + + client, err := New(&idpAccount) + + if err != nil { + t.Errorf("Unable to create browser") + } + + options := client.expectRequestTimeout() + if *options.Timeout != DEFAULT_TIMEOUT { + t.Errorf("Unexpected value for timeout [%.0f]: expected [%.0f]", *options.Timeout, DEFAULT_TIMEOUT) + } +} diff --git a/pkg/provider/f5apm/f5apm.go b/pkg/provider/f5apm/f5apm.go index a57a69555..39a64ea49 100644 --- a/pkg/provider/f5apm/f5apm.go +++ b/pkg/provider/f5apm/f5apm.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/base64" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "strings" @@ -24,7 +24,7 @@ import ( var logger = logrus.WithField("provider", "f5apm") -//Client client for F5 APM +// Client client for F5 APM type Client struct { provider.ValidateBase @@ -126,7 +126,7 @@ func (ac *Client) getSAMLAssertion(loginDetails *creds.LoginDetails) (string, er return "", errors.Wrap(err, "Error retrieving SAML assertion request") } debugHTTPResponse(ac, res) - samlData, err := ioutil.ReadAll(res.Body) + samlData, err := io.ReadAll(res.Body) if err != nil { return "", errors.Wrap(err, "Error reading SAML assertion body") } @@ -211,7 +211,7 @@ func (ac *Client) postLoginForm(loginDetails *creds.LoginDetails, authForm url.V } debugHTTPResponse(ac, res) - data, err := ioutil.ReadAll(res.Body) + data, err := io.ReadAll(res.Body) if err != nil { return nil, errors.Wrap(err, "Error reading response body") } diff --git a/pkg/provider/f5apm/f5apm_test.go b/pkg/provider/f5apm/f5apm_test.go index 5aa065e0f..86ddf879e 100644 --- a/pkg/provider/f5apm/f5apm_test.go +++ b/pkg/provider/f5apm/f5apm_test.go @@ -2,11 +2,11 @@ package f5apm import ( "bytes" - "io/ioutil" "net/http" "net/http/cookiejar" "net/http/httptest" "net/url" + "os" "testing" "github.com/PuerkitoBio/goquery" @@ -18,7 +18,7 @@ import ( ) func TestClient_getLoginForm(t *testing.T) { - data, err := ioutil.ReadFile("example/loginpage.html") + data, err := os.ReadFile("example/loginpage.html") require.Nil(t, err) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -43,7 +43,7 @@ func TestClient_getLoginForm(t *testing.T) { }, authForm) } func TestClient_postLoginForm_user_pass(t *testing.T) { - data, err := ioutil.ReadFile("example/loginpage.html") + data, err := os.ReadFile("example/loginpage.html") require.Nil(t, err) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -68,7 +68,7 @@ func TestClient_postLoginForm_user_pass(t *testing.T) { } func TestClient_containsMFAForm(t *testing.T) { - data, err := ioutil.ReadFile("example/mfapage.html") + data, err := os.ReadFile("example/mfapage.html") require.Nil(t, err) doc, err := goquery.NewDocumentFromReader(bytes.NewReader(data)) require.Nil(t, err) @@ -78,7 +78,7 @@ func TestClient_containsMFAForm(t *testing.T) { } func TestClient_containsMFAForm_False(t *testing.T) { - data, err := ioutil.ReadFile("example/loginpage.html") + data, err := os.ReadFile("example/loginpage.html") require.Nil(t, err) doc, err := goquery.NewDocumentFromReader(bytes.NewReader(data)) require.Nil(t, err) diff --git a/pkg/provider/googleapps/README.md b/pkg/provider/googleapps/README.md index ab2646c0d..743aec050 100644 --- a/pkg/provider/googleapps/README.md +++ b/pkg/provider/googleapps/README.md @@ -4,10 +4,9 @@ This provider uses SAML with Google Apps to enable authentication of users to AW # prerequisites -Setup your Google Apps and AWS Account as per one of the configuration guides. +Setup your Google Workspace Apps and AWS Account: -* [How to Set Up Federated Single Sign-On to AWS Using Google Apps](https://aws.amazon.com/blogs/security/how-to-set-up-federated-single-sign-on-to-aws-using-google-apps/) -* [Using Google Apps SAML SSO to do one-click login to AWS](https://blog.faisalmisle.com/2015/11/using-google-apps-saml-sso-to-do-one-click-login-to-aws/) +* [How to set up IAM federation using Google Workspace](https://aws.amazon.com/blogs/security/how-to-set-up-federated-single-sign-on-to-aws-using-google-workspace/) # configuration diff --git a/pkg/provider/googleapps/example/challenge-extra-number.html b/pkg/provider/googleapps/example/challenge-extra-number.html new file mode 100644 index 000000000..67dcbc6b5 --- /dev/null +++ b/pkg/provider/googleapps/example/challenge-extra-number.html @@ -0,0 +1,96 @@ + + + + + + Google Accounts + + + +
+
+
+
+
+
+
+
+
+

2-Step Verification

+

This extra step shows it’s really you trying to sign in

+
+
+
+
Open the Gmail app on Nicholas’s iPhone
Google sent a notification to your Nicholas’s iPhone. Open the Gmail app, tap Yes on the prompt, then tap 89 on your phone to verify it’s you.
89

After you’ve finished on your phone, press the button below.

Don’t ask again on this device
+
+
+
+
+
+
+
nick@example.comUse a different account
+
+
+
+ +
+
+
+
+
+ + diff --git a/pkg/provider/googleapps/example/challenge-sms-send.html b/pkg/provider/googleapps/example/challenge-sms-send.html new file mode 100644 index 000000000..ac45efe14 --- /dev/null +++ b/pkg/provider/googleapps/example/challenge-sms-send.html @@ -0,0 +1,350 @@ + + + + +
+
+
+
+
+
+
+
+
+
+
+

2-Step Verification

+
+
+

This extra step shows it’s really you trying to sign in

+
+
+
+
+
+ + + + + + + + + + + + + + + +
+ +
+ Get a verification code by text message at: + + (•••) •••-••XX + +
+ +
+ + + Don’t ask again on this device + + +
+
+
+
+
+
+
+ + + + + + + + + + + + + + +
+
+
+ + edw@defang.io + + + Use a different account + +
+
+
+
+ +
+ + + + +
+
+
+
+ \ No newline at end of file diff --git a/pkg/provider/googleapps/example/challenge-sms.html b/pkg/provider/googleapps/example/challenge-sms.html new file mode 100644 index 000000000..f69d14645 --- /dev/null +++ b/pkg/provider/googleapps/example/challenge-sms.html @@ -0,0 +1,371 @@ + + + + +
+
+
+
+
+
+
+
+
+
+
+

+ 2-Step Verification +

+
+
+

+ This extra step shows it’s really you trying to sign in +

+
+
+
+
+
+ + + + + + + + + + + + + + + +
+ +
+ Enter a verification code +
+
+ A text message with a verification code was just sent to + + (•••) •••-••XX + +
+
+
+ G- +
+ +
+ + + Enter a code + + + +
+ + + Don’t ask again on this device + + +
+
+
+
+
+
+
+ + + + + + + + + + + + + + +
+
+
+ + edw@defang.io + + + Use a different account + +
+
+
+
+ +
+ + + + +
+
+
+
+ + + \ No newline at end of file diff --git a/pkg/provider/googleapps/example/form-password-challengeid-1.html b/pkg/provider/googleapps/example/form-password-challengeid-1.html new file mode 100644 index 000000000..e8771fd7f --- /dev/null +++ b/pkg/provider/googleapps/example/form-password-challengeid-1.html @@ -0,0 +1,115 @@ + + + + + + + + + + Google Accounts + + +
+
+
+
+
+
+
+
+
+
+

One account. All of Google.

+

Sign in with your Google Account

+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + test-id1@example.com +
+ +
+ + +
+ +
+ + Stay signed in + +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+
+
+

Sign in with a different account

+

One Google Account for everything Google

+
+
+
+
+
+
+ +
+ + + +
+
+
+
+ + +
diff --git a/pkg/provider/googleapps/example/form-password-challengeid-2.html b/pkg/provider/googleapps/example/form-password-challengeid-2.html new file mode 100644 index 000000000..046c73c1e --- /dev/null +++ b/pkg/provider/googleapps/example/form-password-challengeid-2.html @@ -0,0 +1,115 @@ + + + + + + + + + + Google Accounts + + +
+
+
+
+
+
+
+
+
+
+

One account. All of Google.

+

Sign in with your Google Account

+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + test-id2@example.com +
+ +
+ + +
+ +
+ + Stay signed in + +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+
+
+

Sign in with a different account

+

One Google Account for everything Google

+
+
+
+
+
+
+ +
+ + + +
+
+
+
+ + +
diff --git a/pkg/provider/googleapps/googleapps.go b/pkg/provider/googleapps/googleapps.go index e2ad9e1c7..8409465c0 100644 --- a/pkg/provider/googleapps/googleapps.go +++ b/pkg/provider/googleapps/googleapps.go @@ -55,8 +55,12 @@ func (kc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) return "", errors.Wrap(err, "error loading first page") } + // Google supports only JavaScript-enabled clients + authForm.Set("bgresponse", "js_enabled") + authForm.Set("Email", loginDetails.Username) + // Post email address w/o password, then Get the password-input page passwordURL, passwordForm, err := kc.loadLoginPage(authURL+"?hl=en&loc=US", loginDetails.URL+"&hl=en&loc=US", authForm) if err != nil { return "", errors.Wrap(err, "error loading login page") @@ -64,23 +68,12 @@ func (kc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) logger.Debugf("loginURL: %s", passwordURL) - authForm.Set("Passwd", loginDetails.Password) + passwordForm.Set("Passwd", loginDetails.Password) + passwordForm.Set("TrustDevice", "on") referingURL := passwordURL - if _, rawIdPresent := passwordForm["rawidentifier"]; rawIdPresent { - authForm.Set("rawidentifier", loginDetails.Username) - referingURL = authURL - } - - if v, tlPresent := passwordForm["TL"]; tlPresent { - authForm.Set("TL", v[0]) - } - if v, gxfPresent := passwordForm["gxf"]; gxfPresent { - authForm.Set("gxf", v[0]) - } - - responseDoc, err := kc.loadChallengePage(passwordURL+"?hl=en&loc=US", referingURL, authForm, loginDetails) + responseDoc, err := kc.loadChallengePage(passwordURL+"?hl=en&loc=US", referingURL, passwordForm, loginDetails) if err != nil { return "", errors.Wrap(err, "error loading challenge page") } @@ -161,12 +154,24 @@ func (kc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) if responseDoc.Selection.Find("#passwordError").Text() != "" { return "", errors.New("Password error") } + + if err := isMissing2StepSetup(responseDoc); err != nil { + return "", err + } + return "", errors.New("page is missing saml assertion") } return samlAssertion, nil } +func isMissing2StepSetup(responseDoc *goquery.Document) error { + if responseDoc.Selection.Find("section.aN1Vld ").Text() != "" { + return errors.New("Because of your organization settings, you must set-up 2-Step Verification in your account") + } + return nil +} + func (kc *Client) tryDisplayCaptcha(captchaPictureURL string) (string, error) { // TODO: check for user flag for easy captcha presentation @@ -231,62 +236,7 @@ func (kc *Client) loadFirstPage(loginDetails *creds.LoginDetails) (string, url.V return "", nil, errors.Wrap(err, "failed to build login form data") } - _, loginPageV1 := authForm["GALX"] - - var postForm url.Values - // using a field which is known to be in the original login page - if loginPageV1 { - // Login page v1 - postForm = url.Values{ - "bgresponse": []string{"js_disabled"}, - "checkConnection": []string{""}, - "checkedDomains": []string{"youtube"}, - "continue": []string{authForm.Get("continue")}, - "gxf": []string{authForm.Get("gxf")}, - "identifier-captcha-input": []string{""}, - "identifiertoken": []string{""}, - "identifiertoken_audio": []string{""}, - "ltmpl": []string{"popup"}, - "oauth": []string{"1"}, - "Page": []string{authForm.Get("Page")}, - "Passwd": []string{""}, - "PersistentCookie": []string{"yes"}, - "ProfileInformation": []string{""}, - "pstMsg": []string{"0"}, - "sarp": []string{"1"}, - "scc": []string{"1"}, - "SessionState": []string{authForm.Get("SessionState")}, - "signIn": []string{authForm.Get("signIn")}, - "_utf8": []string{authForm.Get("_utf8")}, - "GALX": []string{authForm.Get("GALX")}, - } - } else { - // Login page v2 - postForm = url.Values{ - "challengeId": []string{"1"}, - "challengeType": []string{"1"}, - "continue": []string{authForm.Get("continue")}, - "scc": []string{"1"}, - "sarp": []string{"1"}, - "checkeddomains": []string{"youtube"}, - "checkConnection": []string{"youtube:930:1"}, - "pstMessage": []string{"1"}, - "oauth": []string{authForm.Get("oauth")}, - "flowName": []string{authForm.Get("flowName")}, - "faa": []string{"1"}, - "Email": []string{""}, - "Passwd": []string{""}, - "TrustDevice": []string{"on"}, - "bgresponse": []string{"js_disabled"}, - } - for _, k := range []string{"TL", "gxf"} { - if v, ok := authForm[k]; ok { - postForm.Set(k, v[0]) - } - } - } - - return submitURL, postForm, err + return submitURL, authForm, err } func (kc *Client) loadLoginPage(submitURL string, referer string, authForm url.Values) (string, url.Values, error) { @@ -326,6 +276,8 @@ func (kc *Client) loadLoginPage(submitURL string, referer string, authForm url.V func (kc *Client) loadChallengePage(submitURL string, referer string, authForm url.Values, loginDetails *creds.LoginDetails) (*goquery.Document, error) { + authForm.Set("bgresponse", "js_enabled") + req, err := http.NewRequest("POST", submitURL, strings.NewReader(authForm.Encode())) if err != nil { return nil, errors.Wrap(err, "error retrieving login form") @@ -387,6 +339,26 @@ func (kc *Client) loadChallengePage(submitURL string, referer string, authForm u return kc.loadResponsePage(secondActionURL, submitURL, responseForm) case strings.Contains(secondActionURL, "challenge/ipp/"): // handle SMS challenge + if extractNodeText(doc, "button", "Send text message") != "" { + responseForm.Set("SendMethod", "SMS") // extractInputsByFormID does not extract the name and value from + + +
  • + +
  • + + + +
    + + + +
    + + Settings + + +
    + + + + +
    + + + +
    + + +
    + +
    + + + diff --git a/pkg/provider/pingone/pingone.go b/pkg/provider/pingone/pingone.go index 6a0d261b6..00ac1d9eb 100644 --- a/pkg/provider/pingone/pingone.go +++ b/pkg/provider/pingone/pingone.go @@ -4,7 +4,7 @@ import ( "context" "encoding/base64" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "time" @@ -196,7 +196,7 @@ func (ac *Client) handleSwipe(ctx context.Context, doc *goquery.Document, _ *htt return ctx, nil, errors.Wrap(err, "error polling swipe status") } - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) if err != nil { return ctx, nil, errors.Wrap(err, "error parsing body from swipe status response") } @@ -248,18 +248,13 @@ func (ac *Client) handleRefresh(ctx context.Context, doc *goquery.Document, _ *h } func (ac *Client) handleFormSelectDevice(ctx context.Context, doc *goquery.Document, res *http.Response) (context.Context, *http.Request, error) { - deviceList := make(map[string]string) - var deviceNameList []string - - doc.Find("ul.device-list > li").Each(func(_ int, s *goquery.Selection) { - deviceId, _ := s.Attr("data-id") - deviceName, _ := s.Find("a > div.device-name").Html() - - logger.WithField("device name", deviceName).WithField("device id", deviceId).Debug("Select Device") - deviceList[deviceName] = deviceId - deviceNameList = append(deviceNameList, deviceName) - }) - + var deviceMap = findDeviceMap(doc) + deviceNameList := make([]string, len(deviceMap)) + i := 0 + for key := range deviceMap { + deviceNameList[i] = key + i++ + } var chooseDevice = prompter.Choose("Select which MFA Device to use", deviceNameList) form, err := page.NewFormFromDocument(doc, "") @@ -267,7 +262,7 @@ func (ac *Client) handleFormSelectDevice(ctx context.Context, doc *goquery.Docum return ctx, nil, errors.Wrap(err, "error extracting select device form") } - form.Values.Set("deviceId", deviceList[deviceNameList[chooseDevice]]) + form.Values.Set("deviceId", deviceMap[deviceNameList[chooseDevice]]) form.URL, err = makeAbsoluteURL(form.URL, makeBaseURL(res.Request.URL)) if err != nil { return ctx, nil, err @@ -347,3 +342,19 @@ func makeAbsoluteURL(v string, base string) (string, error) { } return baseURL.ResolveReference(pathURL).String(), nil } + +func findDeviceMap(doc *goquery.Document) map[string]string { + deviceList := make(map[string]string) + + doc.Find("ul.device-list > li").Each(func(_ int, s *goquery.Selection) { + deviceId, _ := s.Attr("data-id") + deviceName, _ := s.Find("a > div.device-name").Html() + if deviceName == "" { + deviceName, _ = s.Find("button > div.device-name").Html() + } + + logger.WithField("device name", deviceName).WithField("device id", deviceId).Debug("Select Device") + deviceList[deviceName] = deviceId + }) + return deviceList +} diff --git a/pkg/provider/pingone/pingone_test.go b/pkg/provider/pingone/pingone_test.go index b9f48607f..ad3ffcec6 100644 --- a/pkg/provider/pingone/pingone_test.go +++ b/pkg/provider/pingone/pingone_test.go @@ -2,7 +2,8 @@ package pingone import ( "bytes" - "io/ioutil" + "os" + "reflect" "testing" "github.com/PuerkitoBio/goquery" @@ -29,7 +30,7 @@ func TestMakeAbsoluteURL(t *testing.T) { func TestDocTypes(t *testing.T) { for _, tt := range docTests { - data, err := ioutil.ReadFile(tt.file) + data, err := os.ReadFile(tt.file) require.Nil(t, err) doc, err := goquery.NewDocumentFromReader(bytes.NewReader(data)) @@ -40,3 +41,29 @@ func TestDocTypes(t *testing.T) { } } } + +var deviceNameTests = []struct { + file string + expected map[string]string +}{ + {"example/selectdevice.html", map[string]string{"iPhone": "3270134077889335000", "Android": "3964291169487703000"}}, + {"example/selectdevicebutton.html", map[string]string{"iPhone": "3270134077889335000", "Android": "3964291169487703000"}}, +} + +func TestFindDeviceMap(t *testing.T) { + for _, tt := range deviceNameTests { + data, err := os.ReadFile(tt.file) + require.Nil(t, err) + + doc, err := goquery.NewDocumentFromReader(bytes.NewReader(data)) + require.Nil(t, err) + + deviceMap := findDeviceMap(doc) + + eq := reflect.DeepEqual(deviceMap, tt.expected) + + if eq != true { + t.Errorf("expected deviceMap %v to be %v", deviceMap, tt.expected) + } + } +} diff --git a/pkg/provider/shibboleth/README.md b/pkg/provider/shibboleth/README.md index 8b79d8cc9..29ff9db3d 100644 --- a/pkg/provider/shibboleth/README.md +++ b/pkg/provider/shibboleth/README.md @@ -16,4 +16,4 @@ https://idp.example.com/idp/profile/SAML2/Unsolicited/SSO?providerId=urn:amazon: * Tested on: * Shibboleth 3.3 with Duo MFA; - * Shibboleth 4.0.1 with Duo MFA and CSRF tokens. + * Shibboleth 4.0.1, 4.2.1 with Duo MFA and CSRF tokens. diff --git a/pkg/provider/shibboleth/shibboleth.go b/pkg/provider/shibboleth/shibboleth.go index 678afbbb0..5c0e00656 100644 --- a/pkg/provider/shibboleth/shibboleth.go +++ b/pkg/provider/shibboleth/shibboleth.go @@ -4,7 +4,7 @@ import ( "crypto/tls" "fmt" "html" - "io/ioutil" + "io" "log" "net/http" "net/url" @@ -100,7 +100,7 @@ func (sc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) switch sc.idpAccount.MFA { case "Auto": - b, _ := ioutil.ReadAll(res.Body) + b, _ := io.ReadAll(res.Body) mfaRes, err := verifyMfa(sc, loginDetails, loginDetails.URL, string(b)) if err != nil { @@ -196,6 +196,7 @@ func verifyDuoMfa(oc *Client, loginDetails *creds.LoginDetails, duoHost string, req.URL.RawQuery = q.Encode() req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Accept-Language", "en-us,en;q=0.5") res, err := oc.client.Do(req) if err != nil { @@ -276,7 +277,7 @@ func verifyDuoMfa(oc *Client, loginDetails *creds.LoginDetails, duoHost string, return "", errors.Wrap(err, "error retrieving verify response") } - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) if err != nil { return "", errors.Wrap(err, "error retrieving body from response") } @@ -308,7 +309,7 @@ func verifyDuoMfa(oc *Client, loginDetails *creds.LoginDetails, duoHost string, return "", errors.Wrap(err, "error retrieving verify response") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return "", errors.Wrap(err, "error retrieving body from response") } @@ -337,7 +338,7 @@ func verifyDuoMfa(oc *Client, loginDetails *creds.LoginDetails, duoHost string, return "", errors.Wrap(err, "error retrieving verify response") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return "", errors.Wrap(err, "error retrieving body from response") } @@ -372,7 +373,7 @@ func verifyDuoMfa(oc *Client, loginDetails *creds.LoginDetails, duoHost string, return "", errors.Wrap(err, "error retrieving duo result response") } - body, err = ioutil.ReadAll(res.Body) + body, err = io.ReadAll(res.Body) if err != nil { return "", errors.Wrap(err, "duoResultSubmit: error retrieving body from response") } @@ -409,7 +410,7 @@ func parseTokens(blob string) (string, string, string, string, string) { } func extractSamlResponse(res *http.Response) (string, error) { - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) if err != nil { return "", errors.Wrap(err, "extractSamlResponse: error retrieving body from response") } diff --git a/pkg/provider/shibbolethecp/shibbolethecp.go b/pkg/provider/shibbolethecp/shibbolethecp.go index 31e1ca74e..2bfb59cec 100644 --- a/pkg/provider/shibbolethecp/shibbolethecp.go +++ b/pkg/provider/shibbolethecp/shibbolethecp.go @@ -7,7 +7,6 @@ import ( "encoding/base64" "fmt" "io" - "io/ioutil" "net/http" "text/template" "time" @@ -121,9 +120,9 @@ func (c *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) return "", errors.Wrapf(err, "Response code from IDP at %s: %s", res.Status, res.Request.URL) } - bodyBytes, _ := ioutil.ReadAll(res.Body) + bodyBytes, _ := io.ReadAll(res.Body) logger.Debugf("IDP Response: %s", bodyBytes) - res.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // reset + res.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // reset // Step 2: Process the returned // check for SAML_SUCCESS in S:Body/saml2p:Response/saml2p:Status/saml2p:StatusCode/@Value diff --git a/pkg/samlcache/samlcache.go b/pkg/samlcache/samlcache.go index 3fa1ceae9..8eac25ee1 100644 --- a/pkg/samlcache/samlcache.go +++ b/pkg/samlcache/samlcache.go @@ -3,7 +3,6 @@ package samlcache import ( b64 "encoding/base64" "fmt" - "io/ioutil" "os" "path" "path/filepath" @@ -66,7 +65,7 @@ func (p *SAMLCacheProvider) IsValid() bool { } logger = logger.WithField("Cache_file", cache_path) - raw, err := ioutil.ReadFile(cache_path) + raw, err := os.ReadFile(cache_path) if err != nil { logger.Debug("Could not read cache content", err) return false @@ -130,7 +129,7 @@ func (p *SAMLCacheProvider) ReadRaw() (string, error) { cache_path = p.Filename } - content, err := ioutil.ReadFile(cache_path) + content, err := os.ReadFile(cache_path) if err != nil { return "", errors.Wrap(err, "Could not read the cache file path") } @@ -156,7 +155,7 @@ func (p *SAMLCacheProvider) WriteRaw(samlAssertion string) error { if err != nil { return errors.Wrap(err, "Could not write the cache file directory") } - err = ioutil.WriteFile(cache_path, []byte(samlAssertion), SAMLCacheFilePermissions) + err = os.WriteFile(cache_path, []byte(samlAssertion), SAMLCacheFilePermissions) if err != nil { return errors.Wrap(err, "Could not write the cache file path") } diff --git a/pkg/samlcache/samlcache_test.go b/pkg/samlcache/samlcache_test.go index 095b91ce5..323bc286e 100644 --- a/pkg/samlcache/samlcache_test.go +++ b/pkg/samlcache/samlcache_test.go @@ -2,7 +2,6 @@ package samlcache import ( b64 "encoding/base64" - "io/ioutil" "os" "path" "testing" @@ -66,7 +65,7 @@ func TestCanWrite(t *testing.T) { func TestCanRead(t *testing.T) { // create a dummy file - _ = ioutil.WriteFile("example_cache", []byte("testing output"), 0700) + _ = os.WriteFile("example_cache", []byte("testing output"), 0700) p := SAMLCacheProvider{ Filename: "example_cache", @@ -97,7 +96,7 @@ func templateAssertion(t time.Time) (string, error) { ExpiryRFC3339Time: t.Format(time.RFC3339), } - content, _ := ioutil.ReadFile("./assertion_validity_template.gotmpl") + content, _ := os.ReadFile("./assertion_validity_template.gotmpl") tmpl, err := template.New("assertion_validity_template").Parse(string(content)) if err != nil { defer os.Remove(newfile.Name()) diff --git a/pkg/shell/shell.go b/pkg/shell/shell.go index 1a2e158a1..095f6b9d0 100644 --- a/pkg/shell/shell.go +++ b/pkg/shell/shell.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package shell @@ -5,20 +6,18 @@ package shell import ( "os" "os/exec" - "strings" ) // ExecShellCmd exec shell command using the default shell func ExecShellCmd(cmdline []string, envVars []string) error { + return prepCmd(cmdline, envVars).Run() +} - c := strings.Join(cmdline, " ") - - cs := []string{"/bin/sh", "-c", c} +func prepCmd(cs []string, envVars []string) *exec.Cmd { cmd := exec.Command(cs[0], cs[1:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = append(os.Environ(), envVars...) - - return cmd.Run() + return cmd } diff --git a/pkg/shell/shell_test.go b/pkg/shell/shell_test.go index 096035fcf..7555a38a7 100644 --- a/pkg/shell/shell_test.go +++ b/pkg/shell/shell_test.go @@ -1,8 +1,10 @@ +//go:build !windows // +build !windows package shell import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -15,3 +17,27 @@ func TestExecShellCmd(t *testing.T) { assert.Nil(t, err) } + +func TestPrepCmd(t *testing.T) { + + cmd := prepCmd([]string{"echo", "some$TESTTEST", "one two"}, []string{"TESTTEST=123"}) + + var out strings.Builder + cmd.Stdout = &out + err := cmd.Run() + assert.Nil(t, err) + + assert.Equal(t, "some$TESTTEST one two\n", out.String(), "no eval, spaces preserved") +} + +func TestPrepCmdShell(t *testing.T) { + cmd := prepCmd([]string{"sh", "-c", "echo some$TESTTEST one two"}, []string{"TESTTEST=123"}) + + var out strings.Builder + cmd.Stdout = &out + err := cmd.Run() + assert.Nil(t, err) + + assert.Equal(t, "some123 one two\n", out.String(), "var evaled, spaces squashed") + +} diff --git a/saml.go b/saml.go index 001e01c9a..282aedd86 100644 --- a/saml.go +++ b/saml.go @@ -16,15 +16,15 @@ const ( responseTag = "Response" ) -//ErrMissingElement is the error type that indicates an element and/or attribute is -//missing. It provides a structured error that can be more appropriately acted -//upon. +// ErrMissingElement is the error type that indicates an element and/or attribute is +// missing. It provides a structured error that can be more appropriately acted +// upon. type ErrMissingElement struct { Tag, Attribute string } -//ErrMissingAssertion indicates that an appropriate assertion element could not -//be found in the SAML Response +// ErrMissingAssertion indicates that an appropriate assertion element could not +// be found in the SAML Response var ( ErrMissingAssertion = ErrMissingElement{Tag: assertionTag} ) diff --git a/saml2aws.go b/saml2aws.go index 010c42aca..fa10a6c5f 100644 --- a/saml2aws.go +++ b/saml2aws.go @@ -11,6 +11,7 @@ import ( "github.com/versent/saml2aws/v2/pkg/provider/adfs2" "github.com/versent/saml2aws/v2/pkg/provider/akamai" "github.com/versent/saml2aws/v2/pkg/provider/auth0" + "github.com/versent/saml2aws/v2/pkg/provider/authentik" "github.com/versent/saml2aws/v2/pkg/provider/browser" "github.com/versent/saml2aws/v2/pkg/provider/f5apm" "github.com/versent/saml2aws/v2/pkg/provider/googleapps" @@ -37,10 +38,11 @@ var MFAsByProvider = ProviderList{ "Ping": []string{"Auto"}, // automatically detects PingID "PingOne": []string{"Auto"}, // automatically detects PingID "JumpCloud": []string{"Auto", "TOTP", "WEBAUTHN", "DUO", "PUSH"}, - "Okta": []string{"Auto", "PUSH", "DUO", "SMS", "TOTP", "OKTA", "FIDO", "YUBICO TOKEN:HARDWARE"}, // automatically detects DUO, SMS, ToTP, and FIDO - "OneLogin": []string{"Auto", "OLP", "SMS", "TOTP", "YUBIKEY"}, // automatically detects OneLogin Protect, SMS and ToTP - "KeyCloak": []string{"Auto"}, // automatically detects ToTP - "GoogleApps": []string{"Auto"}, // automatically detects ToTP + "Okta": []string{"Auto", "PUSH", "DUO", "SMS", "TOTP", "OKTA", "FIDO", "YUBICO TOKEN:HARDWARE", "SYMANTEC"}, // automatically detects DUO, SMS, ToTP, and FIDO + "OneLogin": []string{"Auto", "OLP", "SMS", "TOTP", "YUBIKEY"}, // automatically detects OneLogin Protect, SMS and ToTP + "Authentik": []string{"Auto"}, + "KeyCloak": []string{"Auto"}, // automatically detects ToTP + "GoogleApps": []string{"Auto"}, // automatically detects ToTP "Shibboleth": []string{"Auto", "None"}, "F5APM": []string{"Auto"}, "Akamai": []string{"Auto", "DUO", "SMS", "EMAIL", "TOTP"}, @@ -134,6 +136,11 @@ func NewSAMLClient(idpAccount *cfg.IDPAccount) (SAMLClient, error) { return nil, fmt.Errorf("Invalid MFA type: %v for %v provider", idpAccount.MFA, idpAccount.Provider) } return onelogin.New(idpAccount) + case "Authentik": + if invalidMFA(idpAccount.Provider, idpAccount.MFA) { + return nil, fmt.Errorf("Invalid MFA type: %v for %v provider", idpAccount.MFA, idpAccount.Provider) + } + return authentik.New(idpAccount) case "KeyCloak": if invalidMFA(idpAccount.Provider, idpAccount.MFA) { return nil, fmt.Errorf("Invalid MFA type: %v for %v provider", idpAccount.MFA, idpAccount.Provider) diff --git a/saml2aws_test.go b/saml2aws_test.go index 51e0955c4..22cd3a013 100644 --- a/saml2aws_test.go +++ b/saml2aws_test.go @@ -3,21 +3,48 @@ package saml2aws import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/versent/saml2aws/v2/pkg/cfg" + "github.com/versent/saml2aws/v2/pkg/creds" ) func TestProviderList_Keys(t *testing.T) { - names := MFAsByProvider.Names() - require.Len(t, names, 17) - + require.Len(t, names, 18) } func TestProviderList_Mfas(t *testing.T) { - mfas := MFAsByProvider.Mfas("Ping") require.Len(t, mfas, 1) +} + +func TestProviderInvalid(t *testing.T) { + account := &cfg.IDPAccount{ + Provider: "foo1", + } + _, err := NewSAMLClient(account) + assert.ErrorContains(t, err, "Invalid provider: foo1") +} + +func TestProviderAzureADInvalidMFA(t *testing.T) { + account := &cfg.IDPAccount{ + Provider: "AzureAD", + } + _, err := NewSAMLClient(account) + assert.ErrorContains(t, err, "Invalid MFA type: ") +} +func TestProviderAzureADMFA(t *testing.T) { + account := &cfg.IDPAccount{ + Provider: "AzureAD", + MFA: "PhoneAppOTP", + } + client, err := NewSAMLClient(account) + assert.Nil(t, err) + loginDetails := &creds.LoginDetails{Username: "testuser", Password: "testtestlol", URL: "https://id.example.com", MFAToken: "123456"} + err = client.Validate(loginDetails) + assert.Nil(t, err) } diff --git a/saml_test.go b/saml_test.go index ea2465316..38338dcfb 100644 --- a/saml_test.go +++ b/saml_test.go @@ -1,7 +1,7 @@ package saml2aws import ( - "io/ioutil" + "os" "testing" "time" @@ -9,7 +9,7 @@ import ( ) func TestExtractAwsRoles(t *testing.T) { - data, err := ioutil.ReadFile("testdata/assertion.xml") + data, err := os.ReadFile("testdata/assertion.xml") assert.Nil(t, err) roles, err := ExtractAwsRoles(data) @@ -17,8 +17,16 @@ func TestExtractAwsRoles(t *testing.T) { assert.Len(t, roles, 2) } +func TestExtractAwsRolesFail(t *testing.T) { + data, err := os.ReadFile("testdata/notxml.xml") + assert.Nil(t, err) + + _, err = ExtractAwsRoles(data) + assert.Error(t, err) +} + func TestExtractSessionDuration(t *testing.T) { - data, err := ioutil.ReadFile("testdata/assertion.xml") + data, err := os.ReadFile("testdata/assertion.xml") assert.Nil(t, err) duration, err := ExtractSessionDuration(data) @@ -26,8 +34,16 @@ func TestExtractSessionDuration(t *testing.T) { assert.Equal(t, int64(28800), duration) } +func TestExtractSessionDurationFail(t *testing.T) { + data, err := os.ReadFile("testdata/notxml.xml") + assert.Nil(t, err) + + _, err = ExtractSessionDuration(data) + assert.Error(t, err) +} + func TestExtractDestinationURL(t *testing.T) { - data, err := ioutil.ReadFile("testdata/assertion.xml") + data, err := os.ReadFile("testdata/assertion.xml") assert.Nil(t, err) destination, err := ExtractDestinationURL(data) @@ -35,8 +51,16 @@ func TestExtractDestinationURL(t *testing.T) { assert.Equal(t, "https://signin.aws.amazon.com/saml", destination) } +func TestExtractDestinationURLFail(t *testing.T) { + data, err := os.ReadFile("testdata/notxml.xml") + assert.Nil(t, err) + + _, err = ExtractDestinationURL(data) + assert.Error(t, err) +} + func TestExtractDestinationURL2(t *testing.T) { - data, err := ioutil.ReadFile("testdata/assertion_no_destination.xml") + data, err := os.ReadFile("testdata/assertion_no_destination.xml") assert.Nil(t, err) destination, err := ExtractDestinationURL(data) @@ -45,7 +69,7 @@ func TestExtractDestinationURL2(t *testing.T) { } func TestExtractMFATokenDuration(t *testing.T) { - data, err := ioutil.ReadFile("testdata/assertion.xml") + data, err := os.ReadFile("testdata/assertion.xml") assert.Nil(t, err) timeObject, err := ExtractMFATokenExpiryTime(data) @@ -54,8 +78,17 @@ func TestExtractMFATokenDuration(t *testing.T) { assert.Equal(t, "2016-09-10T02:59:39Z", timeObject.Format(time.RFC3339)) } +func TestExtractMFATokenDurationFail(t *testing.T) { + data, err := os.ReadFile("testdata/notxml.xml") + assert.Nil(t, err) + + _, err = ExtractMFATokenExpiryTime(data) + + assert.Error(t, err) +} + func TestExtractMFATokenDuration2(t *testing.T) { - data, err := ioutil.ReadFile("testdata/assertion_invalid_date.xml") + data, err := os.ReadFile("testdata/assertion_invalid_date.xml") assert.Nil(t, err) _, err = ExtractMFATokenExpiryTime(data) diff --git a/testdata/notxml.xml b/testdata/notxml.xml new file mode 100644 index 000000000..66c441f4e --- /dev/null +++ b/testdata/notxml.xml @@ -0,0 +1 @@ +??? \ No newline at end of file